Courses › Low-Level Design › Foundations
The Machine-Coding Round
Runnable code in 90 minutes: time plan, code structure, quick tests and extensions.
Many companies now run LLD as a machine-coding round: 60–120 minutes to build working, runnable code, often in stages where each level adds requirements to what you already built. A design that only exists on a whiteboard doesn't count here. What gets graded is code that runs, is easy to extend, and comes with tests.
How these rounds are structured
- Single-shot: a full prompt ("build a parking lot with these features") and 90 minutes. Sometimes there's a demo at the end.
- Progressive levels: level 1 is basic CRUD, and each later level adds a twist like TTL, history, transfers, or undo. Automated tests check each level. These are common at fintech companies and AI labs. Typical examples are an in-memory database, a banking system or a file store.
- Extend existing code: you're given a small codebase and asked to add a feature cleanly.
A time plan for 90 minutes
[!INTERVIEW] Get to running code early A working skeleton by minute 40 is worth more than a perfect design with no code that runs. Build vertically: finish one use case end to end, then the next, rather than writing every class halfway.
Code structure that scores well
models.py # entities and enums: dataclasses, no business logic beyond invariants
strategies.py # interfaces + implementations of the varying behavior (pricing, assignment)
service.py # orchestration: the public API the use cases call
exceptions.py # domain errors: SpotUnavailable, InsufficientFunds
main.py # demo / driver
test_*.py # a few focused testsIn a single shared file, use the same order with clear section comments.
What "clean" means to the grader
- Names from the domain:
assign_spot, notprocess. - Small methods with one job each.
- Domain exceptions instead of returning
Noneor-1for errors. - Enums for statuses and types, and dataclasses (or records) for plain data.
- Dependency injection: pass strategies and clocks in through the constructor so tests can swap them out.
from dataclasses import dataclass, field
from datetime import datetime
from typing import Callable, Protocol
class PricingStrategy(Protocol):
def price(self, minutes: int) -> int: ...
class HourlyPricing:
def __init__(self, cents_per_hour: int):
self.rate = cents_per_hour
def price(self, minutes: int) -> int:
hours = -(-minutes // 60) # round up
return hours * self.rate
@dataclass
class ParkingService:
pricing: PricingStrategy
now: Callable[[], datetime] = datetime.now # tests pass a fake clock
_tickets: dict = field(default_factory=dict)Injecting the clock lets you test "parked for 3 hours" without waiting 3 hours, and interviewers notice it.
Testing quickly
You won't have time for a full test suite. Write a handful of high-value tests:
- The happy path for each core use case.
- One or two edge cases: capacity full, invalid input, a double operation such as returning a book twice.
- One test for the trickiest rule, for example a fee that crosses an hour boundary.
def test_fee_rounds_up_to_next_hour():
assert HourlyPricing(cents_per_hour=200).price(61) == 400Handling the extension
When the interviewer adds "now support X":
- Say where it plugs in before you type: "This is a new pricing strategy, so nothing else changes."
- If it doesn't plug in cleanly, refactor first and say so. Then add the feature.
- Run your tests again.
If an extension forces you to edit five classes, that's a sign the original design was missing an abstraction. Name the problem and fix it. That reflection is part of the signal.
[!WARNING] Ways to lose the round - 30 minutes of diagrams and no code - Over-engineering: abstract factories and generic repositories for a 90-minute exercise - Global mutable state, or business logic in main - Nothing runs at the end, or you never demo it
[!TIP] Practice In-Memory Banking System · In-Memory Cloud File Storage · Spreadsheet with Formulas · Task Scheduler · Snake Game