Courses › Coding Interviews › Patterns
Practical Multi-Part Rounds
Building an in-memory store or bank in levels: spec reading, data models and traps.
More and more companies, especially AI labs, fintech companies and infrastructure startups, have replaced one puzzle-style coding round with a practical, multi-part round. You build something realistic in levels: an in-memory database, a bank, a file store or a task tracker. Each level adds requirements to the code you already wrote. Automated tests often check each level, and your score depends on how far you get with working code.
What a typical round looks like
Level 1: set(key, field, value), get(key, field), delete(key, field)
Level 2: scan(key) and scan_by_prefix(key, prefix), returning sorted results
Level 3: TTL. set_with_ttl(..., timestamp, ttl); reads take a timestamp
Level 4: backup(timestamp) and restore(timestamp, backup_timestamp), keeping the remaining TTLsUsually you only see the next level once the current one passes. The levels get harder, and later levels punish shortcuts taken earlier.
How it differs from algorithm rounds
Strategy
1. Read the spec like a test suite
The spec contains the edge cases the tests will check: what to return for a missing key, how to sort ties, whether to return "" or None. Follow it exactly. A different output format fails a test just as surely as a wrong answer.
2. Choose the data model for later levels
Spend a minute thinking about what's likely to come next. For a key-value store, a nested map key → field → value works for every level. For TTL, store (value, expires_at) from the start, even if level 1 doesn't need it. Adding fields to a flexible model is cheap. Redesigning a rigid one at level 3 isn't.
3. Write clean code quickly
- Small helper methods:
_is_alive(record, ts),_sorted_fields(key) - Dataclasses or named tuples instead of positional tuples once a record has more than two fields
- One place for each rule. TTL checks should live in a single helper used by every read.
from dataclasses import dataclass
@dataclass
class Record:
value: str
expires_at: float | None = None # None = never expires
def alive(self, ts):
return self.expires_at is None or ts < self.expires_at4. Test each level before moving on
Run the provided tests, then add two or three of your own for the edge cases in the spec. A bug in level 1 makes every later level fail too.
5. Refactor at the boundaries between levels
When a new level doesn't fit, take two minutes to refactor first, then add the feature. Patching new logic into the wrong shape gets slower with every level.
[!INTERVIEW] Narrate the trade-offs, briefly These rounds reward speed, so don't over-explain. But one sentence at each level helps: "I'm storing expiry as an absolute timestamp so restore can recompute the remaining TTL." It shows you're designing, not just typing.
Common traps
- Sorting: the spec says "sorted by value descending, then by key ascending". Use a tuple sort key:
key=lambda kv: (-kv[1], kv[0]). - Time: inclusive vs. exclusive expiry (
ts < expires_at). Timestamps are passed in, never read from the clock. - Mutation during iteration: copy the keys before deleting while you loop.
- Shared mutable state between backups: a backup needs a deep copy, or an immutable snapshot.
- Return types:
Nonevs.""vs.False, and lists vs. formatted strings exactly as the spec shows.
Practicing
Pick a domain (key-value store, bank, file system), write four levels of requirements yourself, and build it in 90 minutes. The Machine-Coding Round lesson covers the closely related LLD version.
[!TIP] Practice In-Memory Database · In-Memory Banking System · In-Memory Cloud File Storage · Time-Based Key-Value Store