Behavioral modeling
Sequence diagrams
A sequence diagram shows time: this message, then that one, then the return value. It is the cheapest way to make a use case visible when the class list alone is not enough.
Example: you have been talking for several minutes and the interviewer still cannot see park. An ASCII or whiteboard sequence — Controller → Lot → Strategy → Spot → Ticket — shows the order and who is not in the room.
Few columns
Five speakers. Cut the rest.
Time goes down
Order and return values, not boxes in a row.
One failure
Show where arrows stop.
Why a clock, not a class list sideways
Boxes in a row with no arrows and no time are not a sequence. Show which call happens first, what it returns, and where it stops on failure. Keep the cast small. Five is plenty. If you need twelve, some of those objects are not in this use case.
These are . Do not put network hops on an LLD sequence unless they asked for HLD. A cache is a class with get/put, not a regional Redis box.
Happy path — park
Driver to controller, controller to lot, lot to strategy, occupy, new ticket, save, ticket back to the driver. After you draw it, your code should match the arrows. If ParkingService starts calling pricing during park, either the diagram was a lie or the code wandered.
Happy path — park Driver Controller Lot Strategy Spot Tickets | | | | | | |--park(car)->| | | | | | |--allocate->| | | | | | |--choose(free)->| | | | | |<---- spot -----| | | | | |-----------occupy---------->| | | | |<---- ok -------------------| | | | |-- new Ticket -- | | | |<--ticket--| | | | | |--------------------------------------save-------->| |<--Ticket---| | | | |
Failure — lot full
This is the that actually matters in this problem. Strategy returns empty. Occupy never runs. No ticket is saved. The driver gets an error. Draw the missing arrows on purpose — they are the design.
Failure — lot full Driver Controller Lot Strategy | | | | |--park(car)->| | | | |--allocate->| | | | |--choose()---->| | | |<-- empty ------| | |<-- LotFull-| | |<- error ---| | | No occupy. No ticket. Nothing saved.
Exit has different columns
Reusing the park diagram and scribbling “also pay” in the margin hides the new speakers. Pricing enters. Allocation leaves. Ticket moves ACTIVE → PAID → CLOSED. CLOSED → ACTIVE does not appear on this sketch because it is not a message anyone is allowed to send.
Happy path — exit Driver Controller Tickets Pricing Ticket Lot | | | | | | |--exit(id)->| | | | | | |----get---->| | | | | |<--ticket---| | | | | |--------fee(ticket)---->| | | | |<------- money ---------| | | | |----------------------pay---------->| | | | | ACTIVE→PAID | | |------------------------------------release---->| | |----------------------close-------->| | | | | PAID→CLOSED | | |----save--------------->| | | |<--Receipt--| | | | |
Code that keeps the promise
The diagram is a promise. Park does not price. Full lot does not occupy. The Java below is the happy path and the empty-strategy stop, not a God service.
Ticket park(Vehicle vehicle) {
Optional<Spot> chosen = allocation.choose(lot.freeSpots(), vehicle);
if (chosen.isEmpty()) throw new LotFull(); // failure path: stop
Spot spot = chosen.get();
spot.occupy(vehicle); // never reached when empty
Ticket ticket = Ticket.issue(spot, vehicle);
ticket.activate();
tickets.save(ticket);
return ticket;
}
Receipt exit(TicketId id, Instant now) {
Ticket ticket = tickets.get(id);
ticket.pay(pricing.fee(ticket, now)); // ACTIVE → PAID
lot.release(ticket.spotId());
ticket.close(); // PAID → CLOSED
tickets.save(ticket);
return new Receipt(id, ticket.fee());
}
// A retry that calls ticket.activate() on CLOSED throws.
// Do not draw that arrow. Do not write that assignment.What to leave as a note, not a twelfth column
Two gates, one stall
Two park sequences can interleave on spot 101. That is a concurrency diagram. On this page, write one line: occupy must be atomic. Then point at race conditions and concurrent state transitions. Do not add Redis and a lock manager to the park sketch.
When they ask for the distributed version
Then the interview moved to system design. Gateways, queues, and stores belong on an HLD sequence. Until you make that clear, park is in-process. Mixing the two is how you run out of whiteboard and never finish occupy.
Where CLOSED → ACTIVE would sit — and why it does not
A webhook retry is not a reverse arrow on the exit sequence. If you need a picture, draw a short illegal fragment: Controller → Ticket.activate() ← rejected. Then stop. The machine lives on state machines.
Good signposting
“I will sketch park, then lot-full, then exit. Five columns. Pricing is not in park.”
Common miss
A row of boxes labeled Lot, Ticket, Redis, Kafka, CDN — no arrows, no time, no failure.
Common mistakes
A sequence that is only a class list sideways
Boxes in a row with no arrows and no time are not a sequence. Show which call happens first, what it returns, and where it stops on failure.
Putting network hops on an LLD sequence
Unless they asked for HLD, park is in-process messages. A cache is a class with get/put, not a regional Redis box.
How to draw it in five minutes
- Write the five column names. Cross out anyone who is not in this use case.
- Draw the happy arrows and the return values. Narrate them as you draw.
- Draw the failure where occupy never runs. Leave the empty space visible.
- Start a second sketch for exit. Do not reuse park columns if pricing just entered.
- Check the Java: every arrow is a call; every missing arrow is a missing call.
If the interviewer cannot see park after the sketch, the columns are wrong or time is not going down. Fix the picture before you open another file.
Return values are arrows too
Sketch the ticket coming back, the empty Optional, the LotFull. A sequence with only outbound calls hides the design: occupy never runs because choose returned empty. Draw the leftward arrows.
Read next
Takeaway
Draw park, then the failure where occupy never runs, then exit with different columns. ASCII is enough. Five speakers. Time goes down. The code must match the arrows — including the arrows you refused to draw.