In this lesson, we’ll walk through what the OOD round actually is. A common prompt is “design a parking lot.” You get a shared editor, about forty-five minutes, and a requirement that changes near the end. Most of that time is barely scored. Two or three decisions determine the result, and we want you knowing which ones before you start.
We’ll cover what you receive, what you must deliver, the one thing being measured, and how OOD differs from the other design rounds it gets confused with. Every term of art is defined the first time it appears. By the end you’ll be able to name the artifact this round wants, spot the two or three decisions that carry it, and tell in one question which design round you are actually in.
Start with the words in the title
An object is a bundle of data together with the operations allowed on that data. A parking Spot holds whether it is occupied, and offers the operations that occupy it and free it. The data and the operations travel together.
A class is the template describing one kind of object. An object is one filled-in copy of that template. Spot is the class; the spot on level 2 in row C is an object.
Object-oriented design (OOD) is deciding which classes exist and which one is responsible for each decision the system has to make. So “design a parking lot” is really two questions: when a car arrives, which object decides where it goes, and which object decides what it costs?
OOD and LLD (low-level design) name the same round. The only difference is how much code you are expected to type.
The one thing being measured
The round measures whether the boundaries you draw between objects survive the next requirement.
Not whether you can recite the four pillars, and not whether you know twenty-three patterns. A change is prepared in advance (say, “now add electric-vehicle bays”) and what is scored is whether you answer it by adding a class or by editing six of them.
flowchart TD
R["New requirement:<br/>electric-vehicle bays"] --> G["Good boundary:<br/>add one FeeModel subclass,<br/>edit nothing"]
R --> B["Bad boundary:<br/>edit ParkingLot, Spot, Ticket,<br/>and three more classes"]
The unit of scale here is the number of files touched per requirement change, not queries per second, not gigabytes of stored data. Those belong to a different round. Correctness means an invariant holds across every method call and the asserts you wrote pass, where an invariant is a statement that must be true before and after every operation (“a spot holds at most one vehicle”).
The design move that makes changes cheap is to put an interface (a small named contract with no code behind it, which several classes can each fulfil in their own way) at the point you expect to change most often. In the parking lot the fee rule changes every quarter and the “does this vehicle fit this spot” rule has not changed in years, so FeeModel is an interface and the fit check is a plain method. Tomorrow’s fee change then arrives as a new class instead of an edit.
If that is the one thing scored, the next question is mechanical: what do you actually hand over to earn the score? That is what fixing the inputs and outputs settles.
What goes in, and what comes out
Candidates lose this round by producing the wrong artifact more often than by producing a bad one, so fix the inputs and outputs first.
In, in this order: one deliberately underspecified sentence of prompt (“design a parking lot”); your own clarifying questions, which fill in what the prompt left out; and one requirement change delivered near the end.
Out, all three expected:
- A class diagram: a box per class listing its data and operations, with lines showing which class knows about which.
- Roughly 60–150 lines of code that actually runs.
- A short account of the change in terms of files: what is new, what is untouched, what it costs.
That last account, for the EV change, sounds like: “EV bays are a new SpotType value and a new FeeModel subclass. ParkingLot and Ticket are untouched. The cost is that the pricing rule now lives in a second file, so reading the whole fee flow means opening two.”
The vocabulary
These terms recur throughout the track. Skim them once and return when one comes up.
| Term | In plain words |
|---|---|
| Class / object | The template, and one filled-in copy of it |
| Interface vs implementation | The interface is the promise (operation names and what they return); the implementation is one way of keeping it. Callers depend on the promise, so the implementation can be swapped |
| Encapsulation | The object owns its data and no one reaches in. You call spot.occupy(car); you do not write spot.vehicle = car from outside |
| Coupling | How much one class must know about another’s internals. Low coupling lets you change one without opening the other |
| Cohesion | How well the things inside one class belong together. High cohesion means every field and method serves the same job |
| Composition vs inheritance | Composition means one object holds another and delegates to it, swappable at runtime. Inheritance means one class is a special case of another and reuses its code, fixed when written |
| Invariant | The statement that must never be false, such as one vehicle per spot |
| Cardinality | The count on each end of a relationship: one lot has one-or-more levels, a spot has zero-or-one vehicle |
| UML | Unified Modeling Language, the standard notation for class diagrams — the boxes, arrows, and diamonds |
| Value object | A small object whose whole identity is its contents, compared by what it holds. Two readings of 19.0 °C are the same reading |
| Actor / use case | An actor is anything that starts an operation, including non-humans (a clock, a sensor). A use case is one operation end to end, with the condition that must hold first |
The four pillars are the textbook summary of object-oriented programming: encapsulation (an object hides how it stores data and exposes only operations), abstraction (a caller depends on what an operation promises, not how it is done), inheritance (one class reuses and specialises another), and polymorphism (several kinds of object answer the same call, each in their own way).
SOLID is a five-item checklist for class design. Reciting it scores nothing; the value is in the consequences, which OOP Fundamentals derives from code that fails without them.
| Letter | Principle | In plain words |
|---|---|---|
| S | Single responsibility | A class should have one reason to change |
| O | Open/closed | Adding behaviour should mean adding code, not editing code that already works |
| L | Liskov substitution | Anywhere the base class works, a subclass must work too, without the caller noticing |
| I | Interface segregation | Several small interfaces beat one large one, so nobody depends on operations they never call |
| D | Dependency inversion | The class holding the policy depends on an interface; the concrete implementation is handed in from outside |
The twenty-three patterns are the named, reusable solutions catalogued in the 1994 book Design Patterns. Five appear in this track:
| Pattern | What it does |
|---|---|
| Strategy | Puts a varying rule behind an interface, so a new rule is a new class (a fee model, a thermostat policy) |
| State | Gives an object different behaviour per lifecycle phase, each phase its own class (vending machine, elevator) |
| Factory | One place that decides which concrete class to build from some input (the right Spot subtype from a size code) |
| Observer | Lets objects register to be told when something changed, so the changer need not know who cares (logger, notifier) |
| Composite | Treats a single item and a group of items through the same interface (a file and a folder both answering size()) |
Naming a pattern is worth little on its own; what scores is naming the change it makes cheap.
Which round is this
Four different interviews get called “the design round,” and answering one with another’s material is the most expensive mistake available. One row settles which one you are in: the decisive question.
| Dimension | OOD / LLD (this track) | Classic system design | ML system design | Agent design |
|---|---|---|---|---|
| The artifact | A class diagram and running code | A topology and a data model | A target definition and an operating point | A control loop and its guards |
| The decisive question | What changes next, and who owns it | Read:write ratio | Where the label comes from | Calls per task |
| Unit of cost | Files edited per change | Storage, egress, fan-out | Feature fetch | Inference tokens |
| Scale of the answer | One process, one machine | A fleet | A pipeline | A loop |
The one-line boundary: classic system design asks which machine the data lives on; OOD asks which object owns the decision. They are not a hard round and an easy round, just different questions. Answering an OOD prompt with QPS (queries per second) numbers reads as badly as answering a system design prompt with class diagrams.
So two things you will not be asked for here are capacity estimates and network topology. If the interviewer wants those, they will say “and now it is a distributed service across 500 garages,” which is the moment to switch to the system design material.
This is a coding interview wearing a design hat
If the invite says LLD, machine coding, low-level design, or object-oriented design, and the format is a shared editor, you will be judged on code that runs. The design discussion is the first stretch of a coding round. Three formats exist:
| Format | How to tell | Where the time goes | What “done” means |
|---|---|---|---|
| Shared editor / LLD | CoderPad, HackerRank, “bring your laptop” | ~15 min design, ~30 min code | It executes, with a main that exercises it |
| Whiteboard OOD | No editor mentioned, on-site loop | ~25 min design, ~20 min code sketch | Interfaces and one real method, on the board |
| Machine coding | Two-hour slot, common at some fintechs | 20 min design, 100 min build | A CLI or test suite the interviewer runs |
CoderPad and HackerRank are shared-editor products with a language runtime attached. A main is the entry point that exercises your classes when the file runs. A CLI (command-line interface) is a text menu driven by typing. All three formats score the same design decisions; they differ only in how much code you produce, and the shared-editor version is now the common case. If an interpreter is open, run the code, because an interviewer reads untested code as broken.
What “runs” looks like
Here is the whole bar in one file: a thermostat with a temperature target, a rule for deciding heat/cool/off, and three asserts proving the rule. The rule lives in its own class, and the file checks itself.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
class Mode(Enum):
HEAT = "heat"
COOL = "cool"
OFF = "off"
@dataclass(frozen=True)
class Reading:
celsius: float
class ControlPolicy(ABC):
@abstractmethod
def decide(self, reading: Reading, target: float) -> Mode: ...
class Deadband(ControlPolicy):
"""Do nothing inside +/- width of target, so the compressor does not thrash."""
def __init__(self, width: float) -> None:
self.width = width
def decide(self, reading: Reading, target: float) -> Mode:
if reading.celsius < target - self.width:
return Mode.HEAT
if reading.celsius > target + self.width:
return Mode.COOL
return Mode.OFF
@dataclass
class Thermostat:
target: float
policy: ControlPolicy
def tick(self, reading: Reading) -> Mode:
return self.policy.decide(reading, self.target)
t = Thermostat(target=20.0, policy=Deadband(width=0.5))
assert t.tick(Reading(19.0)) is Mode.HEAT
assert t.tick(Reading(20.2)) is Mode.OFF
assert t.tick(Reading(21.0)) is Mode.COOL
print("thermostat ok")
With a target of 20.0 and a width of 0.5, the deadband runs from 19.5 to 20.5: 19.0 is below it (HEAT), 20.2 is inside it (OFF), and 21.0 is above it (COOL). The three asserts trace the three branches.
Five Python devices in that file recur across the track:
Enum: a closed, named set of constants.Modecan only be heat, cool, or off. No stray strings.@dataclass: a class whose fields are declared once, with the constructor and equality check generated for you.frozen=True: makes the dataclass immutable, turningReadinginto a value object that nothing can mutate after construction.ABCand@abstractmethod: an abstract base class cannot be instantiated and exists to declare the interface;@abstractmethodmarks an operation every subclass must supply. Together they letControlPolicystate a promise with no code behind it.- Dependency injection:
Deadbandis passed into theThermostatinstead of being created inside it, so a different policy needs no edit toThermostat.
The same thing, drawn
Four boxes, three arrows. The one that matters is the solid arrow from Thermostat to ControlPolicy: that is the seam where tomorrow’s requirement arrives.
classDiagram
class Thermostat {
+float target
+tick(Reading) Mode
}
class ControlPolicy {
<<interface>>
+decide(Reading, float) Mode
}
class Deadband {
+float width
}
class Reading {
+float celsius
}
Thermostat "1" --> "1" ControlPolicy : injected
ControlPolicy <|.. Deadband : implements
Thermostat ..> Reading : uses
The marks are UML, and they recur in every chapter: each box is a class with data above operations, + marks what is public, <<interface>> labels a promise with no implementation, the solid arrow with "1" at each end means one thermostat holds exactly one policy, the dashed arrow with a hollow triangle (<|..) reads implements, and the plain dashed arrow (..>) reads uses, mentioned in a signature but not held.
What is present is the shape of a passing answer: an enum, an abstract policy, a value object, one class with one real method, and asserts that state the behaviour. What is absent is deliberate: no getters and setters (one-line methods that only read or write a field, which is a public field with extra ceremony), no do-everything ThermostatManager, no parody AbstractPolicyFactoryBuilder. The deadband is a separate object precisely because the next requirement (“support a schedule that overrides the target overnight”) becomes a new ControlPolicy with zero edits to Thermostat. Fold decide into an if/elif chain inside Thermostat.tick and the same feature edits the class that also owns the target, the sensor wiring, and the display: three times the blast radius.
The shape of the time
The round divides into six blocks. The point of knowing them is to notice when you are behind: roughly twenty of the forty-five minutes are code and extension, so if you have not typed a class by the halfway mark, stop drawing and start typing. When time runs short, what gets cut is drawing, never code.
| Block | Minutes | What it lands |
|---|---|---|
| Clarify and scope | 0–5 | Whether your questions change the design |
| Actors and use cases | 5–10 | Whether you found the operations before the nouns |
| Objects and relationships | 10–20 | The diagram, with cardinalities |
| The 2–3 decisions | 20–25 | The pattern, and the change it makes cheap |
| Code the core | 25–40 | Whether it runs |
| Extend | 40–45 | Whether the design absorbs the change |
flowchart LR
A["Clarify and scope<br/>0-5"] --> B["Actors and use cases<br/>5-10"]
B --> C["Objects and relationships<br/>10-20"]
C --> D["The 2-3 decisions<br/>20-25"]
D --> E["Code the core<br/>25-40"]
E --> F["Extend<br/>40-45"]
The first block carries the whole design, because every class diagram is a set of assumptions with boxes attached: that fee rules will change and matching rules will not, that vehicle sizes are a closed list, that one spot holds one vehicle. Get one wrong and you get the wrong classes, not a tweak. The last block is the point of all the others: the extension question is what a design that can absorb change is built to answer.
What the interviewer has prepared
Three things, in priority order:
- A requirement change, chosen to hit the seam in the obvious design: the joint where a naive model has spread one decision across several classes. In a parking lot it is EV bays or monthly passes; in an elevator, a service mode; in a vending machine, refunds.
- A concurrency question. Concurrency means two things happening at once inside your program, so their steps can interleave: “two cars arrive at the last spot at the same time.” A thread is one independently running sequence of steps; an interleaving is one specific ordering in which two threads’ steps land; a lock is the flag that forces one thread to wait. The answer is never just “add a lock.” It is which object owns the lock, its scope, and what the failing interleaving looks like without it (worked end to end in How to design a parking lot).
- A “why not the simpler thing” question: “why an interface and not a function?” Sometimes the honest answer is that the simpler thing is correct, and saying so is the right answer.
What this round is not
Five habits carried in from other interviews cost time the rubric does not pay for.
| Not this | Because |
|---|---|
| A UML exam | Nobody checks whether you drew a hollow diamond. They check whether the lifetime claim it makes is true |
| A pattern quiz | Naming a pattern is one point; naming the change it makes cheap is three |
| A capacity exercise | One process. If a number matters here it is a cardinality, like “3 entrances,” not a throughput |
| A database schema | Tables come up only if persistence was in scope. Model the objects; mention the mapping in one sentence |
| A LeetCode problem | The data structures are trivial on purpose. A hash map from spot id to spot is the whole algorithm |
Throughput is traffic per second (a system design concern). Persistence means storing data so it outlives the running program, and a schema is the table-and-column layout that holds it. A hash map is a lookup table from key to value with constant-time access. The algorithmic content of an OOD problem is near zero by design, so every minute spent optimizing a lookup is a minute not spent on what is scored.
The prompt space is small
Almost every prompt is one of six shapes, and recognizing the shape early tells you where the hard part is before you have modelled anything.
| Shape | Prompts | The hard part |
|---|---|---|
| Resource allocation | Parking lot, hotel booking, seat reservation, locker | Matching rule, pricing rule, and the race for the last unit |
| State machine | Vending machine, elevator, order lifecycle, ATM | Illegal transitions, and where the transition table lives |
| Rules engine | Chess, tic-tac-toe, blackjack, card games | Separating move legality from execution from win detection |
| Hierarchy / traversal | File system, org chart, comment threads | Composite, and recursion that does not blow the stack |
| Ledger | Splitwise, ATM, wallet, inventory | Money as integers, and an operation that is atomic or absent |
| Feed of events | Logger, notification service, display board | Observer, ordering, and what a slow subscriber does to the producer |
A few of those terms:
- Race: two operations reading the same value and both acting on it, so the last unit gets sold twice.
- State machine: an object with a small set of named states and a fixed table of which moves between them are legal. An unpaid ticket may be paid; a paid one may not be paid again.
- Composite: lets a folder and a file answer the same call so traversal code does not branch. Recursion that blows the stack is a deep tree walked by a function that calls itself once per level until the interpreter gives up.
- Money as integers: storing 5.00 as 500 cents, because binary floating-point cannot represent 0.10 exactly and the errors accumulate. Atomic means the whole operation happens or none of it does.
- Slow subscriber: a listener so slow the producer is stuck waiting for it, which is why the notification path usually needs a queue.
Two of the six put money or a shared resource under concurrent access, and those are the ones where “add a lock” is not an answer. Practice those first: the allocation shape in How to design a parking lot, then any state machine.
Scoring rubric
The weak column stops at the noun; the strong column says what each choice leads to. This is the whole sheet.
| Dimension | Weak | Strong |
|---|---|---|
| Scoping | Starts modelling immediately | Names what is in and what is explicitly out, in one sentence |
| Questions | Trivia with no consequence | Each question is followed by the fork its answer creates |
| Object selection | One class per noun | Triages nouns into classes, enums, value objects, and attributes, with a reason |
| Relationships | Undirected lines | Correct composition / aggregation / association with cardinalities |
| Decisions | Names three patterns | Names one or two, each tied to a specific expected change, each with its cost |
| Code | Pseudocode, or 200 lines of accessors | Runs, has asserts, shows the method where the design lives |
| Concurrency | “Add a lock” | The interleaving, the invariant it breaks, and where the lock goes |
| Extension | “That is easy” | Which file is new, which files are untouched, and why the design made that true |
| Restraint | Interfaces everywhere | Refuses at least one abstraction, because nothing is going to vary |
Three relationship kinds sit behind that “Relationships” row, and they are claims about deletion, not about spelling. Draw composition when the part cannot outlive the whole (delete the level and its spots go with it); draw aggregation when the whole merely holds parts that survive it (delete the lot and the monthly-pass holders still exist); an association is the weakest: one class holds a reference to another and claims nothing about owning it. The notation is worked in OOP Fundamentals. In the “Code” row, accessors is the collective name for getters and setters, and pseudocode is code-shaped prose no interpreter accepts; both fill a screen without making a decision. The Restraint row is the one most people miss: refusing an abstraction (“one kind of spot, and a size enum covers the variation; if a spot ever needs its own behaviour, that is when the interface earns its keep”) is itself a strong answer.
Conclusion
- OOD is deciding which classes exist and which one owns each decision, then writing code that runs.
- The round is scored on one thing: whether your object boundaries absorb the next requirement by adding a class instead of editing several. The unit of cost is files touched per change, not QPS or GB.
- Put the interface where change is expected. If you cannot name a second implementation, do not add the interface.
- Deliver a class diagram, ~60–150 lines that run with asserts, and a short account of a change in terms of files.
- Almost every prompt is one of six shapes; the two involving money or a shared resource are where concurrency is the real test.
One line to carry into the room: this round asks which object owns the decision, and scores you on whether that ownership survives the next requirement.
Further reading
- Gamma, Helm, Johnson, Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (1994), the source of Strategy, State, Factory, Observer, and Composite.
- Robert C. Martin, Agile Software Development, Principles, Patterns, and Practices, the fullest treatment of SOLID.
- Martin Fowler, Refactoring: Improving the Design of Existing Code, on the code smells (large classes, feature envy) that motivate these boundaries.
- Next in this track: OOP Fundamentals, which derives encapsulation, Liskov substitution, coupling, and why Singleton is usually wrong from code that fails without them.