Practice production-shaped Python coding prompts: crawlers, in-memory stores, ledgers, schedulers, parsers, rate limiters, caches, and concurrency follow-ups.
Frontier AI lab coding rounds often look less like isolated puzzle drills and more like small production systems. You may get one base prompt, then a sequence of staged requirements: add TTLs, add concurrency, add cancellation, add rate limits, preserve deterministic output, or explain why your state model will not corrupt itself.
This article is the coding session for the final interview-prep section. The goal is to become fast at practical Python: clear state, small APIs, local tests, and honest concurrency invariants.
Use this loop for every prompt:
Good interview code is not the most abstract code. It is code whose invariants can be defended while requirements change.
Know these without documentation:
| Need | Python building block |
|---|---|
| FIFO work queue | collections.deque, queue.Queue, asyncio.Queue |
| Counts and top errors | collections.Counter |
| LRU cache | collections.OrderedDict |
| Deadlines and TTL | time.monotonic, injected now function |
| Priority scheduling | heapq, queue.PriorityQueue |
| Thread safety | threading.Lock, threading.RLock, threading.Condition, threading.Event |
| Worker fanout | concurrent.futures.ThreadPoolExecutor, as_completed |
| Parsing | splitlines, re, explicit state machines |
Do not wait for a test framework. Write a small run_tests() and use plain assert.
Use these as drills. Do not memorize wording. Learn the patterns.
| Prompt | Base implementation | Follow-ups |
|---|---|---|
| Same-host web crawler | BFS/DFS with visited set | concurrency, per-host rate limit, timeouts, cancellation |
| In-memory key/value DB | set, get, delete, scan | TTL, compare-and-set, transactions, snapshots |
| Banking ledger | accounts, deposit, withdraw, transfer | idempotency, reversals, scheduled transfers, deadlock avoidance |
| Task scheduler | dependency graph and ready queue | cycle detection, retries, worker pool, cancellation |
| Log parser | multiline event grouping | malformed lines, rolling windows, top errors |
| Rate limiter | fixed or sliding window | token bucket, multi-dimensional quotas, retry-after |
| LRU/TTL cache | capacity eviction | TTL, thread safety, metrics, stale cleanup |
Rate limiters show up because they combine state, boundary conditions, and production behavior. Retry policies often need jitter to avoid synchronized retry storms, so this prompt is really about overload control as much as counters.[1] A strong implementation injects time so tests do not sleep.
1from dataclasses import dataclass
2
3@dataclass
4class Bucket:
5 capacity: float
6 refill_per_second: float
7 tokens: float
8 updated_at: float
9
10class TokenBucketLimiter:
11 def __init__(self, capacity: int, refill_per_second: float) -> None:
12 self.capacity = float(capacity)
13 self.refill_per_second = float(refill_per_second)
14 self._buckets: dict[str, Bucket] = {}
15
16 def allow(self, key: str, now: float, cost: float = 1.0) -> tuple[bool, float]:
17 bucket = self._buckets.get(key)
18 if bucket is None:
19 bucket = Bucket(self.capacity, self.refill_per_second, self.capacity, now)
20 self._buckets[key] = bucket
21
22 elapsed = max(0.0, now - bucket.updated_at)
23 bucket.tokens = min(bucket.capacity, bucket.tokens + elapsed * bucket.refill_per_second)
24 bucket.updated_at = now
25
26 if bucket.tokens >= cost:
27 bucket.tokens -= cost
28 return True, 0.0
29
30 missing = cost - bucket.tokens
31 retry_after = missing / bucket.refill_per_second
32 return False, retry_after
33
34limiter = TokenBucketLimiter(capacity=3, refill_per_second=1.0)
35print([limiter.allow("org-a", now=0.0)[0] for _ in range(4)])
36print(limiter.allow("org-a", now=0.5))
37print(limiter.allow("org-a", now=1.0))1[True, True, True, False]
2(False, 0.5)
3(True, 0.0)What to say out loud:
now is injected for deterministic tests._buckets and bucket mutation.A crawler prompt tests graph traversal plus concurrency. The invariant is simple: each URL is claimed once before it is fetched or enqueued.
1from collections import deque
2from urllib.parse import urlparse, urljoin
3
4PAGES = {
5 "https://lab.example/start": ["/a", "/b", "https://other.example/x"],
6 "https://lab.example/a": ["/b", "/c"],
7 "https://lab.example/b": ["/c"],
8 "https://lab.example/c": [],
9}
10
11def get_urls(url: str) -> list[str]:
12 return PAGES.get(url, [])
13
14def same_host_crawl(start_url: str) -> list[str]:
15 start_host = urlparse(start_url).netloc
16 queue = deque([start_url])
17 visited = {start_url}
18 ordered: list[str] = []
19
20 while queue:
21 url = queue.popleft()
22 ordered.append(url)
23 for raw_link in get_urls(url):
24 link = urljoin(url, raw_link)
25 if urlparse(link).netloc != start_host:
26 continue
27 if link in visited:
28 continue
29 visited.add(link)
30 queue.append(link)
31
32 return ordered
33
34print(same_host_crawl("https://lab.example/start"))1['https://lab.example/start', 'https://lab.example/a', 'https://lab.example/b', 'https://lab.example/c']Concurrency follow-up:
visited with a lock.Ledger prompts test whether you can keep money-like state consistent. Use append-only events when possible; if you maintain balances, update balance and event together.
1from dataclasses import dataclass
2
3@dataclass(frozen=True)
4class Event:
5 idempotency_key: str
6 account: str
7 delta: int
8
9class Ledger:
10 def __init__(self) -> None:
11 self.balance: dict[str, int] = {}
12 self.events: list[Event] = []
13 self.seen: set[str] = set()
14
15 def apply(self, key: str, account: str, delta: int) -> int:
16 if key in self.seen:
17 return self.balance.get(account, 0)
18 new_balance = self.balance.get(account, 0) + delta
19 if new_balance < 0:
20 raise ValueError("insufficient funds")
21 self.balance[account] = new_balance
22 self.events.append(Event(key, account, delta))
23 self.seen.add(key)
24 return new_balance
25
26ledger = Ledger()
27print(ledger.apply("deposit-1", "acct", 100))
28print(ledger.apply("deposit-1", "acct", 100))
29print(ledger.apply("withdraw-1", "acct", -30))
30print(ledger.balance["acct"], len(ledger.events))1100
2100
370
470 2Transfer follow-up:
When asked to make a solution concurrent, say this before writing code:
X.X.This sounds mechanical because it should. Concurrency interview failures usually come from vague ownership.