Skip to main content

Framework: An OO Design Interview Framework

A four-step framework for OO design interviews — narrow scope, model the core entities, sketch the API, then defend extension. Most candidates name classes too early.

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:

  1. 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.
  2. Identifying the core entities. Did the candidate name the right 4–6 nouns, or 14 nouns at the wrong granularity?
  3. 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.
  4. 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 categoryWhat to askWhy 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.

BucketBecomesTest
EntityA class with identity (id) and lifecycleDoes it persist? Can it change?
ValueA field, type, or value objectIs it just a name, amount, or label?
EdgeA 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 (then Sensor becomes 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. VehicleCar, 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:

  1. RateStrategy exists because step 1 surfaced “rules may differ by tenant” as a constraint. Without that constraint, billing is one method on Ticket and we don’t need the strategy. The strategy is justified by the constraint, not by a generic preference for patterns.
  2. Ticket.close() returns a Payment. We don’t model Payment as 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, PaymentProcessor would 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:

  1. 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.
  2. 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 Discount value on Transaction; 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 Tenant entity that owns Machine, 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:

  1. 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.
  2. Over-using inheritance and patterns. Any senior loop is suspicious of a candidate who reaches for AbstractFactory or 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.