The 6-Step Delivery Method
A low-level design interview gives you roughly forty minutes to turn a one-line prompt into a working class design. The six-step method below divides that time into a fixed sequence — clarify requirements, identify entities, design classes, implement, verify, and extend — with a rough time budget and a defined output for each step. Working through the steps in order ensures the problem is understood before any code is written.
Every case study in this course follows these six steps. The timings are approximate; the order is fixed.
Step 1: Requirements (~5 minutes)
The first step is to establish what the system must do. Restate the prompt in your own words to confirm your understanding, then ask two or three questions that set the boundary between what is in scope and what is not.
For a hotel room booking system, such questions are concrete. Are payments handled here, or by a separate checkout service? Do guests reserve specific room numbers, or room types? Are cancellations and refunds in scope? Each answer either removes a class that would otherwise be built or identifies one that would have been missed.
Record both lists, in scope and out of scope. The out-of-scope list documents a deliberate scoping decision and leaves a defined place to add requirements later. Defining classes before the requirements are clear risks modeling the wrong system, with little time left to correct it.
Step 2: Entities and Relationships (~3 minutes)
Once the requirements are fixed, read them for nouns. The significant nouns are the candidate
entities: for the hotel system, Guest, Room, RoomType, and Reservation. The next task is
to determine ownership — which entity holds a reference to which — before defining any field or
method.
This step requires no code. A short description is enough: a Reservation links one Guest to
one Room for a date range; a Room belongs to exactly one RoomType; the Hotel holds the
rooms and the reservations. Describing the model at this point allows the interviewer to correct
it before further work depends on it.
Step 3: Class Design (~10–15 minutes)
Step 3 takes the largest share of the time. For each entity, derive its state (fields) and behavior (methods) from the requirements identified in Step 1. A field is included only when a specific requirement calls for it.
The governing principle is Tell, Don't Ask: an object should act on its own state rather than
expose it for callers to inspect. A Room with a public available flag requires every caller to
check the flag before booking. A Room whose book(reservation) method checks availability
internally keeps that rule in a single location.
Work through the entities in order, listing fields and method signatures only. The hotel's Room
holds a room_number, a room_type, and a status of AVAILABLE, OCCUPIED, or MAINTENANCE.
Its methods are book(), check_out(), and set_maintenance(), each of which guards the
transition and rejects it when the room is in the wrong state.
Step 4: Implementation (~10 minutes)
Once the design is fixed, implement the happy path first: the sequence of calls a successful booking makes, from search to confirmation. Edge-case guards — double booking, invalid dates, a missing room — are added after that path works.
A complete core with clear method signatures is more useful than extensive but unfinished edge-case handling. When time is limited, implement one or two methods fully and leave the rest as stubs, each annotated with the check it would perform.
The two versions below show the difference. The first skips the framework; the second follows it.
1# Bad: jumped straight to code without requirements or entity design.
2# Room number is a string, dates are strings, no status guard, no error handling.
3class Hotel:
4 def __init__(self):
5 self.rooms = {}
6
7 def book(self, room_id, guest, start, end):
8 self.rooms[room_id] = {"guest": guest, "start": start, "end": end}
1from enum import Enum, auto
2from datetime import date
3
4class RoomStatus(Enum):
5 AVAILABLE = auto()
6 OCCUPIED = auto()
7 MAINTENANCE = auto()
8
9# Good: status is an enum, transition is guarded, error is explicit.
10class Room:
11 def __init__(self, room_number: str, room_type: str) -> None:
12 self.room_number = room_number
13 self.room_type = room_type
14 self._status = RoomStatus.AVAILABLE
15 self._active_reservation: "Reservation | None" = None
16
17 @property
18 def status(self) -> RoomStatus:
19 return self._status
20
21 def book(self, reservation: "Reservation") -> None:
22 if self._status is not RoomStatus.AVAILABLE:
23 raise ValueError(f"Room {self.room_number} is {self._status.name}")
24 self._status = RoomStatus.OCCUPIED
25 self._active_reservation = reservation
26
27 def check_out(self) -> None:
28 if self._status is not RoomStatus.OCCUPIED:
29 raise ValueError(f"Room {self.room_number} is not occupied")
30 self._status = RoomStatus.AVAILABLE
31 self._active_reservation = None
Step 5: Verification (~5 minutes)
With the core implemented, trace one concrete scenario through the objects, step by step. For the
hotel system: a guest searches for an available double room for three nights, the system finds
one, a Reservation is created, Room.book() runs, and the room's status becomes OCCUPIED. The
guest later checks out, Room.check_out() runs, and the status returns to AVAILABLE.
Tracing a scenario aloud reveals gaps in the design. If a step in the scenario has no corresponding method — for example, returning a room to availability — that method is missing and can be added. The trace also makes the reasoning visible to the interviewer, who can correct an incorrect scenario before it affects the final design.
Step 6: Extensibility (~5 minutes)
The final step is to take one plausible new requirement and show that the design accommodates it
without significant change. For the hotel system, consider adding loyalty pricing for repeat
guests. If pricing logic is embedded in Hotel.book(), that method must be modified. If pricing
is delegated to a PricingPolicy attached to the reservation, the change is a new
LoyaltyPricingPolicy, and Room and Hotel are unaffected.
The extension does not need to be implemented. Identifying the point at which the change would be made is enough to show that the design separates pricing from the rest of the system.
Putting it together
The six steps are a guideline rather than a strict script. In practice, Steps 1 and 2 overlap, because entities emerge while requirements are discussed, and Steps 4 and 5 interleave, because a method is often tested as it is written. The order of concerns is fixed, however: understand before designing, design before coding, code before verifying, and verify before extending.
The next article applies all six steps to a restaurant table-reservation system, showing the output of each one.