Engineering Atlas › Low-Level Design

Solving a machine coding round

11 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

What the round is and how it differs from OOD

Many product companies, especially in India, run a machine-coding round: a problem such as a parking lot, a meeting room booker or a Splitwise clone, your own IDE, and a fixed window. At the end you demo it, the evaluator feeds it inputs, then reviews the code for structure and how easily it absorbs a change. The object-oriented design round (covered in the companion lesson on approaching OOD interviews) is a conversation with diagrams where code is optional. Here working code is required, and the design is judged by reading it. The score depends on three things, in order: does it run and print the right output, can a reviewer find each rule in one place, and does the next requirement fit without surgery.

A time plan

For a 90-minute round:

MinutesActivityOutput
0 to 10Read twice, list commands, ask questionsCommands with exact output formats
10 to 15Sample input as a failing test; sketch entitiesOne golden test, a few class names
15 to 30Models, repositories, parser, first command end to endSomething that runs
30 to 65One vertical slice per remaining commandAll happy paths pass
65 to 80Validation, error lines, edge-case testsBad input never crashes
80 to 90Run the full sample, rename, delete dead codeDemo ready

For 120 minutes, keep the shape and spend the extra time on edge cases and the "optional" requirement. If two commands are missing at minute 60, stop polishing and finish them.

Ask only questions that change structure or output: case-sensitive keywords, exact error text, half-open intervals, tie-breaks, id reuse. Write each answer at the top of the test file; it becomes your spec.

Scaffolding order

  1. Models. Plain data: Room(name, capacity), Booking(id, user, room, start, end).
  2. Repositories. In-memory stores with add, get, remove and the one or two queries the rules need (for_room). They own id generation and contain no business rules.
  3. Services. Every rule lives here: "smallest room that fits", "only the owner can cancel". They take repositories in the constructor, raise domain errors, and never print or parse.
  4. Parser and dispatcher. Tokenise, look the keyword up in a table of name -> (arity, handler), check the count, convert numbers, call the service, format the result. This is the Command pattern in its lightest form (see Command).
  5. Driver. Read lines, call execute, print. No logic, so no test needed.

In-memory repositories

A map keyed by id gives O(1) lookups, which is enough for nearly every round. Hiding the map behind methods lets you answer "how would you persist this?" with "implement this interface against a table" rather than "rewrite the service". Advance the id counter only when a write succeeds, so a rejected command does not shift every later output.

Command parsing that does not crash

Treat every line as hostile: blank, extra spaces, lowercase keyword, wrong argument count, a word where a number belongs. Validate in a fixed order (keyword, arity, number format, domain rules) so the error for a bad line is predictable. Domain errors share one type; the dispatcher catches that type, prints ERROR <message> and moves on.

Tests

A golden test feeds the spec's sample input and compares the full output; it catches format slips that evaluators' scripts are unforgiving about. Focused tests cover each rule and its edges: ties, boundaries, owner checks, and "a failed command changes nothing". Inject anything nondeterministic so tests repeat, as Design Snake and Ladder Game does with its dice and Design Movie Booking System with hold expiry.

Extensibility the evaluator will probe

With a dispatch table a new command is one entry plus a service method. The rule most likely to vary (which spot, which room, how to split) belongs behind a strategy: Design Parking Lot does this for spot choice and Design Splitwise for split types. Do not pre-build interfaces for everything; make the two likely changes cheap (see YAGNI Principle).

The same shape elsewhere

An interpreter over in-memory state is the finding things fast shape (keyword to handler, id to entity) wrapped around a state machine. Redis is exactly this: a command table mapping each name to a handler and an arity, and a loop that parses, checks and runs each request against in-memory data.

Down the stack

Layer 1The contract

Every input line produces exactly one output line, or none for a blank or comment line. Output is a pure function of the lines seen so far. A rejected command emits ERROR ... and leaves every repository and id counter unchanged. No input line can terminate the process. The practice tests check each statement.

Layer 2The mechanism

Whitespace tokenising is O(line length). The dispatcher is a hash map from the uppercased keyword to an arity and a handler. Redis stores the same idea: a positive arity means exactly that many arguments including the name, a negative one means at least that many, and the server checks it before calling the handler. Numbers pass a strict digits-only check, so -5, 1e3 and 12abc fail the same way. The service reads repositories and writes only after every check passes, which makes a failed command atomic without rollback code.

Layer 3The machine

Go's bufio.Scanner stops with "token too long" on lines over 64 KiB unless you call Buffer, a surprise when a sample file holds one huge line. Writing is the bigger trap: Go's os.Stdout is unbuffered, so fmt.Println per line is one write syscall, around a microsecond each; 100,000 lines spend a tenth of a second in syscalls alone. Wrap it in a bufio.Writer and flush at exit. Splitting allocates: Python creates a new string object (about 50 bytes of header) per token, while Go's strings.Fields returns substrings sharing the line's bytes.

Why it's built this way

  1. Evaluators judge by running sample input, so exact output and "never crash" matter more than any pattern.
  2. Running something early is the only defence against the clock, so build vertical slices, not one layer at a time.
  3. Each rule must be findable and testable alone, so rules live in services, away from parsing and printing.
  4. The last 20 minutes usually bring a change request, so commands, the main allocation rule and storage sit behind a table, a strategy and a repository.

Diagram

flowchart LR
  IN["Input line"] --> TOK["Tokenise"]
  TOK --> DISP{"Keyword in table?"}
  DISP -- no --> ERR["ERROR line"]
  DISP -- yes --> AR{"Arity and numbers ok?"}
  AR -- no --> ERR
  AR -- yes --> SVC["Service: rules"]
  SVC -- "domain error" --> ERR
  SVC --> REPO["In-memory repositories"]
  SVC --> FMT["Format result"]
  FMT --> OUT["Output line"]
  ERR --> OUT

Worked example

The spec: a meeting room booker with ADD_ROOM, BOOK, CANCEL, FREE and SCHEDULE; book the smallest room that fits, ties by name; 90 minutes. Minutes 0 to 10: the candidate asks whether intervals are half-open (yes), whether ids advance on failed bookings (no) and whether lowercase keywords are allowed (yes). Minutes 10 to 15: the sample goes into a golden test:

ADD_ROOM big 10      -> ADDED big
ADD_ROOM small 4     -> ADDED small
BOOK alice 540 600 3 -> BOOKED B1 small
BOOK bob 540 600 3   -> BOOKED B2 big
BOOK cara 540 600 3  -> ERROR no room available

Minutes 15 to 30: models, two repositories and the dispatcher with only ADD_ROOM; it runs. Minutes 30 to 60: the other four commands, about 7 minutes each. Minutes 60 to 80: error paths, half-open boundaries (540-600 and 600-660 coexist) and a test that a rejected booking does not consume B3. The change request, "rooms can go under maintenance", is one dispatch entry, a blocked flag and one condition in the free check.

Trade-offs

ChoiceStrengthsWeaknessesChoose when
Single fileFast to startHard to read past 300 linesTwo or three commands
Layered (models, repos, services, commands)Each rule has one homeTen minutes of scaffolding firstAlmost every round
Exceptions for domain errorsOne catch in the dispatcherA broad catch hides bugsPython, TypeScript, Java
Secondary indexFast queriesKept in sync on every writeSpec mentions large inputs

Pitfalls in production

Leadership lens

Check yourself

  1. At minute 60 you have three of five commands working and want to refactor a long handler. What should you do?

    Show answer

    Finish the two missing commands first. A missing command fails the evaluator's run outright, while a long handler costs a little readability.

  2. Why should the repository own id generation, and when should the counter advance?

    Show answer

    Ids are a storage concern and must be unique across all writers. The counter advances only when a write happens, so rejected commands do not shift later ids.

  3. Your dispatcher catches all exceptions and prints ERROR plus the message. What is the risk?

    Show answer

    A real bug prints a plausible error line and tests may still pass. Catch only the domain error type so bugs crash in tests.

  4. How do you make a failed BOOK leave no trace without rollback code?

    Show answer

    Run every validation and the room search before any write, so the only write is the final insert.

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →