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 TTLs

Usually 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_at

4. 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: None vs. "" 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