Object-oriented design interviews look like a friendly cousin of
system design — same length, same whiteboard, but classes instead
of services. They aren’t. The mistake mode is different. In system
design, candidates over-scope. In OO design, candidates under-scope
— they hear “design a parking lot” and start sketching Vehicle,
Car, Truck, Motorcycle on the board within 90 seconds.
The position of this framework: at senior level, OO design rounds test whether you can resist the urge to name classes for as long as possible. The candidate who spends 6 minutes asking what the parking lot actually does — entry mechanics, payment, multi-tenancy, the shape of the data the operator queries — almost always lands the round. The candidate who starts naming classes at minute 2 spends the rest of the hour defending choices made before the requirements were clear.
This framework is for senior, staff, and engineering-manager loops where OO design appears as a 45–60 minute round. It is not for new-grad rounds, where the bar is “can you produce reasonable classes” and the framework’s overhead isn’t worth it.
The problem
OO design rounds reward different things than they appear to. The surface-level signal is “can the candidate produce a clean class diagram.” The actual calibration signal is closer to four distinct things, mostly tested before any code goes on the board:
- Scoping under ambiguity. Did the candidate narrow the problem before designing, or did they assume a default version and dive in? The default version is usually wrong for the company asking.
- Identifying the core entities. Did the candidate name the right 4–6 nouns, or 14 nouns at the wrong granularity?
- Designing for the right interface boundary. Where does this thing live? What calls it? The boundary determines what’s a class versus a method versus a parameter.
- Defending extension. When a follow-up requirement lands (“now support hourly and monthly billing”), does the design absorb it cleanly, or does it require restructuring?
Three of these four are tested in the first 15 minutes, before any class diagram exists. Most candidates spend those 15 minutes producing a class diagram instead.
The framework is four steps, in strict order. The first three each get a hard time budget; if you violate the budget you hand back points to the candidate who didn’t.
The framework: clarify, model, structure, extend
Step 1: Clarify (8–10 minutes)
Surface the requirements that will determine class boundaries before naming any class. The questions are not optional and are not filler — each one rules out a class structure that would otherwise look reasonable.
Use this checklist, in this order:
| Question category | What to ask | Why it matters for OO |
|---|---|---|
| Scope | ”Is this a single instance or multi-tenant?” | Multi-tenant collapses or expands the top-level container |
| Lifecycle | ”What’s the read pattern? Mostly writes? Mostly queries?” | Determines whether to model as commands or as state |
| Persistence | ”Are we designing the in-memory model only, or does this persist?” | Persistence forces an id/identity decision early |
| Concurrency | ”Is this single-threaded or concurrent?” | Concurrency forces immutability decisions on core entities |
| Authorization | ”Who can do what to what?” | Permission usually wants its own class, not a method on the entity |
| Extension axes | ”Will the rules differ by [tenant / region / type]?” | Determines polymorphism vs configuration |
For each answer, write a one-line note on the board. The interviewer sees you converting their words into design constraints in real time — that’s the visible signal you’re being calibrated on.
The trap to avoid: nodding through the answers without writing them down. The board is not for showing off — it’s for forcing the follow-up question. When you write “single-threaded, in-memory only, single tenant” on the board, you have committed to a scope, and the interviewer either confirms it or pushes back. Either is good signal. Nodding generates no signal and you’ll forget half of it before minute 20.
The output of step 1 is a 4–6 line constraint summary at the top of the board. Read it back to the interviewer before moving on. Most candidates skip this step entirely; the ones who don’t have the round halfway won by minute 10.
Step 2: Model (10–12 minutes)
Now identify the nouns and their relationships — still without naming classes. The distinction matters: a noun becomes a class only when it has identity, behavior, or constraints worth carrying. A noun that’s just a value (an amount, a duration, a name) is a field on something else. A noun that’s just a reference (a list of IDs) isn’t a class either.
The two-pass method:
Pass 1: List every noun that came up in step 1. Write all of
them on the right side of the board. Don’t filter yet. For a parking
lot: lot, level, spot, vehicle, ticket, entry, exit,
payment, rate, operator, tenant, gate, sensor. Twelve to
twenty nouns is normal.
Pass 2: Triage each noun into one of three buckets.
| Bucket | Becomes | Test |
|---|---|---|
| Entity | A class with identity (id) and lifecycle | Does it persist? Can it change? |
| Value | A field, type, or value object | Is it just a name, amount, or label? |
| Edge | A relationship (composition, association) | Does it just connect two entities? |
For the parking lot:
- Entities:
Lot,Level,Spot,Vehicle,Ticket,Payment - Values:
Rate,LicensePlate,Money - Edges:
entry,exit,gate,sensor— these are events or operations, not entities, unless you’ve been told sensor failure is a domain concern (thenSensorbecomes an entity)
Six entities for a parking lot is right. Nine to twelve is over-modeled. Three is under-modeled. The senior signal is restraint — the candidate who triaged out four entities and can explain why scores higher than the candidate who modeled all of them.
The board now has constraints (from step 1) and a triaged noun list (from step 2). Still no class diagram. You’re at minute 22; you have 23 minutes left.
The trap to avoid in this step: introducing inheritance hierarchies
before the constraints justify them. Vehicle → Car, Truck,
Motorcycle is the canonical wrong move. Until step 1 surfaces a
difference in behavior between these (different rates? different
spot sizes? different parking rules?), they are values, not
subclasses. If they have the same behavior, type is a field, not a
class.
Step 3: Structure (15–20 minutes)
Now sketch the classes — fields, key methods, and the one relationship line between each pair of related classes. Not full UML; just enough to make the structure legible.
The sequencing rule: structure outward from the entity that owns
identity for the operation the question is asking about. For a
parking lot, the question is usually “park a vehicle and produce a
ticket” — the operation owns identity, so start with Ticket
(or whichever entity carries the operation’s lifecycle). For a
file system, it’s File or Directory. For a chess game, it’s
Game (because moves are scoped to a game, and the game holds the
state).
For each entity, write only:
- 2–4 key fields (
id, identity-bearing fields, the obvious payload) - 2–4 key methods (verbs, in the language the domain uses)
- One relationship line per related entity (“Ticket has 1 Spot”, “Lot contains many Levels”)
You are not writing constructors, getters/setters, full type signatures, or every field. Senior calibration deducts for verbosity at this stage; the interviewer wants to see structure, not boilerplate.
When a method’s logic is non-trivial, write it as one line of
pseudocode under the method name. Three lines of pseudocode beats
ten lines of method signatures. Example for Ticket.close():
“compute duration → look up Rate by spot type → produce Payment”.
The discipline check, every time you add a method: ask yourself does this method change with a likely future requirement? If yes, the variation point belongs somewhere — a strategy, a configuration, a separate class. If no, leave it inline. Most candidates over-apply this; the rule is to surface the one or two variation points the question implies, not all of them.
For a parking lot with billing as the implied variation point:
Ticket
fields: id, spot_id, vehicle_id, entered_at, closed_at
methods: close() -> Payment, duration() -> minutes
RateStrategy (interface)
rate_for(ticket: Ticket) -> Money
HourlyRate : flat per-hour
DailyRate : day cap with hourly fill
MonthlyRate : pre-paid; rate_for returns 0
Two key calls here:
RateStrategyexists because step 1 surfaced “rules may differ by tenant” as a constraint. Without that constraint, billing is one method onTicketand we don’t need the strategy. The strategy is justified by the constraint, not by a generic preference for patterns.Ticket.close()returns aPayment. We don’t modelPaymentas a service; it’s an entity produced by the close operation, and payment processing is out of scope (we asked about scope in step 1; the answer was “in-memory model only”). If the answer had been different,PaymentProcessorwould exist.
That’s the round in microcosm: every class on the board is on the board because of a specific constraint surfaced in step 1, and every class not on the board is absent for the same reason.
Step 4: Defend extension (5–8 minutes)
The last 5–8 minutes belong to the interviewer’s follow-up (“now support reservations” / “now support hourly and monthly billing” / “now support multi-tenant rate cards”). This is the explicit test of whether the design absorbs the change cleanly.
The framework’s prescription: don’t redraw. Walk through where the new requirement lands in the existing structure, and only redraw the parts that genuinely need restructuring.
For “now support monthly billing” on the parking lot design above:
the answer is “add a new MonthlyRate strategy” — three lines on
the board, no restructuring. That’s the win condition.
For “now support reservations” on a design that doesn’t have
reservations: a new Reservation entity, a new state on Ticket
(“reserved” → “active” → “closed”), and a small change to
Lot.find_spot() to honor reservations. That’s a real but bounded
change — also a win.
For “now support multi-tenant rate cards” on a single-tenant design:
this is the case where the constraint surfaced in step 1 was wrong,
and you say so directly. “This design assumed single-tenant. To
support multi-tenant, Lot needs a Tenant reference, RateStrategy
needs a tenant scope, and Spot.is_available() needs to filter by
tenant.” Naming the constraint that breaks is a senior-level move;
hand-waving a redesign is not.
Two things to remember in this step:
- Patch first, restructure only when necessary. Senior signal is showing the design is robust, not that you can produce a second design under time pressure.
- Name the constraint that broke. If a follow-up requires restructuring, identify the original constraint that ruled out the now-needed structure. This shows you understand the design was a function of constraints, not an arbitrary choice.
How to apply it: a worked example end-to-end
Question: “Design a vending machine.”
Step 1 (8 min): Clarifying questions, in order.
- “Single machine or fleet management?” → Single machine.
- “Cash, card, or both?” → Both, plus mobile pay.
- “Inventory tracking — does the machine know stock levels?” → Yes, it should detect empty slots.
- “What happens when payment is partial?” → Return change or refund.
- “Concurrency? Two people pressing buttons at once?” → Single user at a time, but the machine must handle interrupted transactions.
- “Persistence?” → In-memory model. Persistence is out of scope.
Constraint summary on the board:
- single machine, single user at a time
- cash + card + mobile pay
- inventory tracked per slot
- partial payment → change/refund
- transactions can be interrupted
- in-memory only
Step 2 (10 min): Triaged nouns.
- Entities:
Machine,Slot,Product,Transaction,PaymentMethod - Values:
Money,ProductId,SlotId - Edges:
dispense,pay(operations on Transaction)
Coin, Bill, Card, MobileWallet are not entities — they
collapse into PaymentMethod polymorphism only if their behaviors
differ. They do (cash returns change in coins, card needs
authorization), so we keep PaymentMethod as an interface and have
the implementations follow. Decision recorded on the board.
Step 3 (18 min): Structure.
Machine
slots: List<Slot>
active_txn: Transaction?
start(product_id) -> Transaction
cancel() -> Refund
Slot
id, product_id?, quantity
is_empty() -> bool
dispense() -> Product
Product
id, name, price (Money)
Transaction
id, slot, paid: Money, status
pay(method, amount) -> result
complete() -> Product
cancel() -> Refund
PaymentMethod (interface)
authorize(amount) -> result
capture(amount) -> result
refund(amount) -> result
Cash : authorize is no-op; refund returns coins
Card : two-step auth/capture
Mobile : delegates to wallet provider
The interrupted-transaction constraint pushes us to an explicit
status on Transaction (started, paid, dispensed, cancelled,
refunded). Without that constraint, Transaction could be simpler.
The constraint justified the field.
Step 4 (8 min): Follow-ups.
- “What about discount codes?” → Adds a
Discountvalue onTransaction;Transaction.complete()applies it before capture. No restructuring. - “Now multi-tenant — different operators with different
inventory?” → “This design assumed single-machine. Multi-tenant
pushes us to a
Tenantentity that ownsMachine, and slot inventory becomes scoped per tenant. The transaction model is unchanged because transactions are still single-user.” Naming what changes versus what doesn’t is the senior signal.
That round lands at level. The class diagram is small. The constraint reasoning is heavy. That’s the right ratio.
Where this framework breaks
Tightly time-boxed (30-minute) rounds. With only 30 minutes, step 1 has to compress to 4–5 minutes and step 4 to ~3. The framework still applies in shape; the budgets shrink. Don’t skip step 1 entirely — even at 30 minutes, the constraint cost of designing the wrong thing is higher than the cost of a tight clarifying pass.
Code-heavy rounds where the interviewer expects working code. Some companies (notably parts of Google’s L5+ loops, and some machine-learning-engineer loops) ask for OO design and a working implementation of one method. The framework runs through step 3, then drops into a focused implementation of the one method the interviewer picks — usually the method with the most non-trivial logic. Don’t try to implement everything; the implementation pass is selective by design.
Domain-modeling rounds where the answer is “a state machine, not classes.” Some questions (parsers, protocols, game engines) are naturally state machines. Forcing them into class hierarchies produces awkward designs. If steps 1–2 surface a clear state machine, name it directly: “I’d model this as a state machine — let me draw the states first, then the classes that hold them.” The interviewer reads this as design judgment, not deviation.
Design-pattern interviews. Some companies (rare but real) ask “design X using the Decorator pattern” or “model this with Visitor.” That’s pattern recall, not design. The framework’s constraint-first move doesn’t apply; what’s being tested is canonical-pattern fluency. Recognize the round shape and switch modes.
Senior+ AI/ML domain rounds. ML-flavored OO rounds (e.g., “design a feature store API”) sometimes look like OO design but are really lightweight system design with classes. Apply the framework, but expect the boundary in step 3 to land at a service, not an in-process class. The interface — not the class hierarchy — is the load-bearing artifact.
What this framework is really saying
The two failure modes the OO design round selects against are:
- Designing before knowing the problem. The candidate who names classes at minute 2 has committed to a problem shape, and the rest of the round is them defending or re-deriving choices the interviewer didn’t ask for.
- Over-using inheritance and patterns. Any senior loop is
suspicious of a candidate who reaches for
AbstractFactoryor a four-level class hierarchy without a constraint to justify it. The signal is judgment, not pattern fluency.
The framework’s central move — clarify before model, model before structure, structure before extend — is structurally just a forcing function for those judgments. You can’t over-design at minute 4 if you’ve committed to spending minutes 0–8 on clarifying questions. You can’t add a four-level hierarchy if you’ve triaged nouns into entity/value/edge first. The framework is the scaffolding that keeps the judgment visible while the design is being built.
The candidate who internalizes this stops needing the framework within a few rounds — the moves become natural. Until then, run it explicitly. The visible structure on the board is itself part of the signal.
Related
- Framework: Thinking About Tradeoffs in System Design — the system-design analog of this framework. The clarify-first move is the same; the modeling layer is at services rather than classes.
- Framework: The Behavioral Interview for Senior+ — the OO design round leaks behavioral signal too, especially judgment. How you handle the “now support X” follow-up is read as a judgment story.
- Plan: Complete Senior Backend Interview Prep — where OO design fits in the broader prep allocation.