---
title: Delivery framework for LLD
description: Five phases and a bonus round for taking a low level design from a blank page to working classes, with a tic-tac-toe example worked through end to end.
tags: [lld, oop, design, interview]
---

Run a low level design question in five phases: requirements, entities and flows,
interfaces, implementation, walkthrough, then extensibility as a sixth. Each phase
feeds the next, so you are never inventing structure on the spot.

## The phases

| # | Phase              | You leave with                                            |
| - | ------------------ | --------------------------------------------------------- |
| 1 | Requirements       | Scope, constraints, assumptions, what is out              |
| 2 | Entities and flows | The nouns, their attributes, who owns whom, the use cases |
| 3 | Interfaces         | Class names, signatures, responsibilities. No bodies      |
| 4 | Implementation     | Bodies for the methods that matter                        |
| 5 | Walkthrough        | One flow traced through the code you just wrote           |
| 6 | Extensibility      | Where a new requirement plugs in                          |

Phase 6 is the one to drop when the work has to stop somewhere. The other five are
the design itself.

The example below is tic-tac-toe, small enough that the rules need no explaining.

## Phase 1: Requirements

Ask until you can write the spec down and have whoever owns the problem agree with it.
Four
things to pin: what it must do, what the rules are, what happens when someone breaks
a rule, and what you are not building.

For tic-tac-toe:

* Two players alternate on a 3x3 board, X goes first.
* A player wins with three of their symbol in a row, column, or diagonal.
* A full board with no winner is a draw.
* Playing on a taken cell, or playing once the game is over, is rejected, and the turn
  does not pass.
* Out of scope: persistence, networking, AI opponent, more than two players.

Say the out of scope list out loud, otherwise the question grows while you are
answering it.

## Phase 2: Entities and flows

Pull the nouns out of the spec, give each one attributes and a single
responsibility, then say how they connect. Do the flows in the same breath: narrate
a use case and the missing entities show up on their own.

Entities:

* `Board` holds a grid of cells and knows what is at each position.
* `Player` has a symbol, X or O.
* `Game` owns the board and the two players, and tracks whose turn it is.

Relationships: one game owns one board and exactly two players. The board owns its
cells. Nothing points back upward, so there are no cycles to reason about.

Flows: start a game, take a turn, reject an invalid turn, end on a win, end on a draw.

Keep the ownership direction explicit. The usual mistake here is a child holding a
reference back to its parent for no reason.

## Phase 3: Interfaces

Turn entities into classes. For each one, decide state and behaviour, and write the
signatures. No method bodies yet: this phase is about what each class promises, and
bodies hide that behind detail.

```java
public enum Symbol { X, O }

public enum GameStatus { IN_PROGRESS, WON, DRAW }

public class Board {
    private final Symbol[][] cells;

    public Board(int size) { }
    public boolean isEmpty(int row, int col) { }
    public void place(int row, int col, Symbol symbol) { }
    public Symbol at(int row, int col) { }
    public boolean isFull() { }
}

public class Player {
    private final Symbol symbol;

    public Player(Symbol symbol) { }
    public Symbol symbol() { }
}

public interface WinStrategy {
    boolean hasWon(Board board, Symbol symbol);
}

public class Game {
    private final Board board;
    private final List<Player> players;
    private final WinStrategy winStrategy;
    private int currentPlayerIndex;
    private GameStatus status;

    public Game(Board board, List<Player> players, WinStrategy winStrategy) { }
    public void play(int row, int col) { }
    public GameStatus status() { }
    public Player currentPlayer() { }
}
```

Work top down from the class that orchestrates, here `Game`. Its method list tells
you what everything below it has to provide.

`WinStrategy` is an interface rather than a method on `Board` because the win rule is
the part most likely to change. That is a guess about the future, so justify it in one
sentence and move on.

## Phase 4: Implementation

Fill in the methods that carry the logic. Skip getters. Happy path first, then the
rejections.

```java
public void play(int row, int col) {
    if (status != GameStatus.IN_PROGRESS) {
        throw new IllegalStateException("Game is over");
    }
    if (!board.isEmpty(row, col)) {
        throw new IllegalArgumentException("Cell is taken");
    }

    Symbol symbol = currentPlayer().symbol();
    board.place(row, col, symbol);

    if (winStrategy.hasWon(board, symbol)) {
        status = GameStatus.WON;
        return;
    }
    if (board.isFull()) {
        status = GameStatus.DRAW;
        return;
    }
    currentPlayerIndex = (currentPlayerIndex + 1) % players.size();
}
```

Both guards run before the turn advances, so a rejected move leaves the same player on
turn. That was a requirement from phase 1.

```java
public class LineWinStrategy implements WinStrategy {
    private final int lineLength;

    public LineWinStrategy(int lineLength) { }

    @Override
    public boolean hasWon(Board board, Symbol symbol) {
        // check every row, column, and both diagonals for `lineLength` in a row
    }
}
```

Leaving a body as a comment is fine when the logic is a loop anyone can picture. Spend
the time on the methods where a reviewer would disagree with you.

## Phase 5: Walkthrough

Take a flow from phase 2 and run it through the code. This catches the errors that
reading cannot.

X plays (0,0). Status is in progress, cell is empty, X is placed. No win, board is not
full, turn passes to O. O plays (1,1), same path. X plays (0,1), then O plays (2,2),
then X plays (0,2). `hasWon` finds X across the top row, status becomes `WON`, and the
turn never advances, so the board freezes with X as the winner.

Then try to break it. O calls `play(0,0)` after the win and the first guard throws.

## Phase 6: Extensibility

Take a requirement that was out of scope and show where it lands. If the answer is a
new class, or a different implementation of an existing interface, the design held.

* 4x4 board: `new Board(4)`. Board was never hardcoded to 3.
* Connect four style, four in a row: `new LineWinStrategy(4)`. Game does not change.
* A bot player: make `Player` an interface with a `nextMove` method, add a second
  implementation. Game still calls the same thing.
* Undo: `Game` keeps a stack of moves, `Board` gains a `clear(row, col)`. This one
  touches existing classes, so say so rather than pretending it is free.

Undo is the useful one to raise. Saying which change does not fit cleanly is worth more
than claiming every change is a one liner.

## What usually goes wrong

People rush phase 3 because writing signatures feels slower than writing code. It is
the phase worth the most of your attention.

The other common miss is phase 5. A short trace catches mistakes that rereading the
code will not.
