---
title: Comprehensive Preparation Guide for Canva PLF Python Interview: DevOps and Platform Engineer Roles
created: 2026-03-25
updated: 2026-03-25
type: summary
tags: ["devops", "interview", "notes", "python", "telegram"]
external: http://go/ui/posts/2260/
---

Now I have a comprehensive picture. Let me synthesise everything into a targeted guide.

---

# 🐍 Canva PLF (Python) Interview — DevOps / Platform Engineer Prep Guide

## What the PLF Round Actually Is

Based on what candidates have reported across Glassdoor and Canva's own hiring pages:

The PLF interview asks you to implement a class they've come up with — it's **not** a LeetCode-style algorithm grind. Canva's interviews focus more on practical tasks you'd do on the job rather than memorising algorithms. For a Platform/DevOps engineer in Python, this means tasks like: building a tool, automating something, implementing a service abstraction, or writing a class that models real infrastructure behaviour.

Canva now expects candidates to demonstrate skill with AI coding tools like Copilot, Cursor, and Claude during technical interviews — piloting a process where candidates solve challenges that require genuine engineering judgment even with AI assistance. These problems can't be solved with a single prompt; they require iterative thinking, requirement clarification, and good decision-making.

**The key insight for PLF:** They are testing *how you write Python*, not just whether you can produce output. Clean code, idiomatic Python, OOP design, error handling, and talking through your reasoning are all being assessed.

---

## 🎯 The 5 Most Likely Question Archetypes for Platform/DevOps Python

Based on Canva's role patterns and confirmed interview reports, here are the most probable question types:

---

### 1. 🏗️ Implement a Class / OOP Design

**The most common PLF format.** You're given a domain (cache, rate limiter, job scheduler, config store) and asked to implement it as a Python class with methods.

**Example question:**
> *"Implement a `RateLimiter` class that limits API calls to N requests per time window. Support per-key limiting."*

**Model answer:**

```python
import time
from collections import defaultdict, deque

class RateLimiter:
    """Token-bucket style rate limiter with per-key support."""

    def __init__(self, max_requests: int, window_seconds: float):
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self._timestamps: dict[str, deque] = defaultdict(deque)

    def is_allowed(self, key: str) -> bool:
        now = time.monotonic()
        window = self._timestamps[key]

        # Evict timestamps outside the current window
        while window and now - window[0] > self.window_seconds:
            window.popleft()

        if len(window) < self.max_requests:
            window.append(now)
            return True
        return False

    def remaining(self, key: str) -> int:
        """How many requests are left in this window."""
        now = time.monotonic()
        window = self._timestamps[key]
        while window and now - window[0] > self.window_seconds:
            window.popleft()
        return max(0, self.max_requests - len(window))
```

**What to say out loud:**
> *"I'm using a sliding window approach — I'll store timestamps per key in a deque. On each call I evict stale entries then check the count. The tradeoff vs a fixed window is it's slightly more memory-intensive but much more accurate at window boundaries."*

---

### 2. 🔄 Build a CLI Tool / Automation Script

**Very common for platform engineers.** You'll be asked to build a tool that interacts with files, processes logs, or manages config.

**Example question:**
> *"Write a Python CLI tool that reads a directory of `.log` files, extracts ERROR lines between two timestamps, and outputs a summary."*

**Model answer:**

```python
import argparse
import re
from pathlib import Path
from datetime import datetime

LOG_PATTERN = re.compile(
    r'(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (?P<level>\w+) (?P<message>.+)'
)

def parse_log_file(path: Path, start: datetime, end: datetime) -> list[dict]:
    errors = []
    with path.open() as f:
        for line in f:
            m = LOG_PATTERN.match(line.strip())
            if not m:
                continue
            ts = datetime.fromisoformat(m.group('timestamp'))
            if m.group('level') == 'ERROR' and start <= ts <= end:
                errors.append({'timestamp': ts, 'message': m.group('message'), 'file': path.name})
    return errors

def main():
    parser = argparse.ArgumentParser(description='Summarise ERROR logs between two timestamps')
    parser.add_argument('log_dir', type=Path)
    parser.add_argument('--start', required=True, help='ISO format: 2024-01-01 00:00:00')
    parser.add_argument('--end', required=True)
    args = parser.parse_args()

    start = datetime.fromisoformat(args.start)
    end = datetime.fromisoformat(args.end)

    all_errors = []
    for log_file in args.log_dir.glob('*.log'):
        all_errors.extend(parse_log_file(log_file, start, end))

    all_errors.sort(key=lambda e: e['timestamp'])
    print(f"Found {len(all_errors)} ERROR(s) between {start} and {end}:\n")
    for e in all_errors:
        print(f"  [{e['file']}] {e['timestamp']} — {e['message']}")

if __name__ == '__main__':
    main()
```

---

### 3. 🧵 Concurrency / Async — Infrastructure Context

Platform engineers deal with concurrent workloads. Expect questions involving threading, async I/O, or worker pools.

**Example question:**
> *"Write a Python function that health-checks a list of URLs concurrently and returns a dict of `{url: status}`."*

**Model answer:**

```python
import asyncio
import aiohttp
from typing import NamedTuple

class HealthResult(NamedTuple):
    url: str
    status: int | None
    error: str | None = None

async def check_url(session: aiohttp.ClientSession, url: str, timeout: float = 5.0) -> HealthResult:
    try:
        async with session.get(url, timeout=aiohttp.ClientTimeout(total=timeout)) as resp:
            return HealthResult(url=url, status=resp.status)
    except Exception as e:
        return HealthResult(url=url, status=None, error=str(e))

async def health_check_all(urls: list[str]) -> dict[str, HealthResult]:
    async with aiohttp.ClientSession() as session:
        tasks = [check_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
    return {r.url: r for r in results}

# Entry point
if __name__ == '__main__':
    urls = ['https://canva.com', 'https://google.com', 'https://nonexistent.xyz']
    results = asyncio.run(health_check_all(urls))
    for url, result in results.items():
        status = result.status or f"ERROR: {result.error}"
        print(f"{url}: {status}")
```

**What to say:** *"I'll use asyncio + aiohttp for true async I/O — much better than threading for network-bound tasks. I'm using `gather` so all checks run concurrently rather than sequentially."*

---

### 4. 🗄️ Data Structure / Algorithm with DevOps Flavour

Not raw LeetCode, but practical algorithmic thinking applied to infrastructure problems.

**Example question:**
> *"Given a stream of deployment events `{service, timestamp, status}`, implement a class that can efficiently answer: 'what services are currently in a FAILED state?'"*

```python
from dataclasses import dataclass
from datetime import datetime
from collections import defaultdict

@dataclass
class DeployEvent:
    service: str
    timestamp: datetime
    status: str  # 'SUCCESS' | 'FAILED' | 'DEPLOYING'

class DeploymentTracker:
    def __init__(self):
        # Track latest event per service
        self._latest: dict[str, DeployEvent] = {}

    def record(self, event: DeployEvent) -> None:
        existing = self._latest.get(event.service)
        if existing is None or event.timestamp > existing.timestamp:
            self._latest[event.service] = event

    def failed_services(self) -> list[str]:
        return [svc for svc, ev in self._latest.items() if ev.status == 'FAILED']

    def status(self, service: str) -> str | None:
        ev = self._latest.get(service)
        return ev.status if ev else None
```

---

### 5. 🐍 Python Fluency — Language Knowledge Questions

The interviewer may test specific Python knowledge inline. Key topics for platform engineers:

| Topic | What They'll Ask |
|---|---|
| **Decorators** | Write a `@retry(times=3)` decorator |
| **Context managers** | Implement `__enter__`/`__exit__` for a DB connection |
| **Generators** | Parse a large log file without loading it all into memory |
| **Dataclasses / typing** | Model infra config with proper type hints |
| **Error handling** | Custom exception hierarchy for an API client |

**Example — Retry Decorator:**
```python
import time
import functools

def retry(times: int = 3, delay: float = 1.0, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == times:
                        raise
                    print(f"Attempt {attempt} failed: {e}. Retrying in {delay}s...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(times=3, delay=0.5, exceptions=(ConnectionError,))
def fetch_data(url: str) -> str:
    # ... actual implementation
    pass
```

---

## ✅ What Canva Specifically Evaluates in PLF

Based on real candidate feedback, here's the scoring rubric they likely use:

| Signal | What Good Looks Like |
|---|---|
| **Code fluency** | Idiomatic Python — list comps, type hints, dataclasses, context managers |
| **OOP design** | Clean class hierarchy, single responsibility, proper `__init__` |
| **Error handling** | Doesn't ignore exceptions; uses specific exception types |
| **Communication** | Talks through trade-offs while coding, doesn't go silent |
| **Testing instinct** | Mentions edge cases, may write a quick test or assertion inline |
| **Pragmatism** | Doesn't over-engineer; asks clarifying questions before going deep |

---

## ⚠️ Common Pitfalls (Learned from Canva Rejections)

One candidate reported being rejected despite writing fluent code, because they took 2 minutes to think silently before starting — the interviewer interpreted this as poor communication. When asked if they needed help, saying "no" was also flagged negatively as lacking collaboration signals.

**The lesson:** At Canva's PLF round, **communication is part of the assessment even during coding.** Narrate your thinking from the start, even if you're just saying *"Let me think through the data model before I write any code..."*

---

## 🚀 Your 1-Week Prep Checklist

- [ ] Practice implementing 2–3 classes from scratch with full type hints and docstrings
- [ ] Write at least one async Python script using `asyncio`
- [ ] Practice the retry decorator, context manager, and generator patterns cold
- [ ] Time yourself: can you implement a clean class with error handling in 20 minutes while talking out loud?
- [ ] Review Python's `collections` module — `defaultdict`, `deque`, `Counter`, `namedtuple`
- [ ] Brush up on `pathlib`, `argparse`, and `dataclasses` — all very common in platform tooling

Want me to drill you on any of these question types with a mock problem you can practice live?
