Syllabus · G1
Reading and drawing a class diagram under time pressure
The idea
Reading and drawing a class diagram under time pressure
An interviewer says, "before you write any code, sketch the design." Thirty seconds later you are standing at a whiteboard with a marker, and the clock the rest of the round runs on has already started. What you draw in that window either buys a shared vocabulary for the next twenty minutes, or it costs two minutes later. Redrawing happens the moment a question arrives that your first sketch cannot answer.
corpus/parking-lot's ParkingLot class holds a SpotGrid, a SpotAllocator, and a PricingPolicy. Boxes and lines are the honest first move: draw four rectangles, one per class, and connect ParkingLot to each of the other three with a plain line. That sketch is not wrong, and it takes fifteen seconds to draw.
It cannot answer the interviewer's next likely question, some version of "what happens to the grid if the lot is deleted." A plain line does not say. SpotGrid is created once, inside ParkingLot's own constructor, and nothing else in the program ever holds a reference to it: delete the lot and the grid goes with it. SpotAllocator and PricingPolicy are different. ParkingLot receives them from outside, in its constructor, and a second ParkingLot could be handed the exact same FirstFitAllocator instance. Losing this distinction on the diagram costs the ability to say which cleanup code, if any, a shutdown path needs to run.
UML has a name for each side of that distinction, and drawing them differently is worth the ten extra seconds. A filled diamond at the owner's end means composition: the part's lifetime is bound to the whole, so ParkingLot *-- SpotGrid. A plain arrowhead means association: one object holds a reference to another that lives independently of it, so ParkingLot --> SpotAllocator. A hollow diamond, aggregation, sits between the two. It marks a whole that holds parts it did not create and does not own outright, useful when a collection is shared rather than injected fresh each time. Three arrows, and each answers one question. Does the whole create and destroy the part? Does the whole merely hold a part it received from elsewhere? Or is there no ownership question at all, because the box on the other end is a rule the object calls out to rather than a part of it.
The second question worth ten seconds: is SpotAllocator a class or an interface. It is an interface, FirstFitAllocator is its one implementation today, and the requirements already hint a second is coming. Draw that relationship as a dashed line with a hollow triangle pointing at the interface. This is realization, and it flags the exact seam B1 teaches: the box most likely to gain a second implementation before the interview ends.
Multiplicities close the last gap. ParkingLot holds zero or more open Stay records at once, so the composition arrow to Stay carries 1 at the lot's end and 0..* at the stay's end. Skip a multiplicity wherever the count is fixed at one by definition, and write it wherever a reviewer would otherwise have to ask.
That is the whole subset worth carrying into a fifty-minute round: boxes, three arrows, and a multiplicity wherever the count is not one. Skip the visibility marks (+public, -private) and the full method signatures inside the box. Skip dependency arrows kept separate from association, and skip notes and stereotypes. Skip every diagram type this problem does not need: sequence, state, activity, package. None of those change what the interviewer learns from the sketch, and every one of them costs seconds a round does not give back.
worked/ draws corpus/parking-lot's five classes end to end, one requirement at a time, in the same order this page did. faded/completion.md runs the drill in both directions on corpus/vending-machine: code to diagram, then diagram to class skeleton, because a candidate meets both directions inside the same round.
C1 is why each box here is one job and nothing more. B1 is the seam a realization arrow exists to flag in the first place.
Worked walkthrough
What each arrow says, and what a plain line hides
One file, one class, five steps. Run it:
.toolchain/jdk-21/bin/javac -Xlint:all -d out lessons/G1/worked/Main.java
.toolchain/jdk-21/bin/java -cp out Main
javac -Xlint:all prints nothing. java -cp out Main prints the five steps below, in order, unedited.
--- Step 1 (0:15) - boxes only
+--------------+ +-----------+ +---------------+ +--------------+
| ParkingLot | | SpotGrid | | SpotAllocator | | PricingPolicy|
+--------------+ +-----------+ +---------------+ +--------------+
Four rectangles, no lines yet. Correct, and it answers nothing an
interviewer would ask next.
Boxes only is a real, defensible thirty-second sketch. It names the four classes a reader of corpus/parking-lot/reference/src/ParkingLot.java would find in its constructor, and it takes no risk, because it makes no claim about how any of them relate.
--- Step 3 (0:35) - composition vs association: who owns whom
ParkingLot "1" *-- "1" SpotGrid (composition: built inside the constructor)
ParkingLot "1" --> "1" SpotAllocator (association: injected, may outlive the lot)
ParkingLot "1" --> "1" PricingPolicy (association: injected, may outlive the lot)
ParkingLot's own constructor is the reason for the split:
public ParkingLot(SpotGrid grid, SpotAllocator allocator, PricingPolicy pricing) {
this.grid = Objects.requireNonNull(grid, "grid");
this.allocator = Objects.requireNonNull(allocator, "allocator");
this.pricing = Objects.requireNonNull(pricing, "pricing");
}
All three arguments arrive the same way, by constructor injection, so a diagram that stopped at step 2's plain arrows would draw all three identically. The convenience constructor is where the difference actually lives:
public ParkingLot(int standardSpots) {
this(new SpotGrid(standardSpots), new FirstFitAllocator(), new FlatHourlyPricing());
}
new SpotGrid(standardSpots) is built right there and handed to nothing else in the program. new FirstFitAllocator() and new FlatHourlyPricing() are exactly as disposable in this one call site, but the general constructor above accepts any SpotAllocator a caller already built and might reuse for a second lot. The filled diamond on SpotGrid says its lifetime is promised to end with the lot's, which is true whichever constructor built it. The plain arrows on the other two say no such promise is made, which is also true of both constructors. Drawing all three as one kind of arrow would erase a fact a reviewer can ask about directly: "does SpotGrid outlive a ParkingLot, and can two lots share one?"
--- Step 4 (0:45) - realization: SpotAllocator is an interface
FirstFitAllocator ..|> SpotAllocator
FlatHourlyPricing ..|> PricingPolicy
SpotAllocator.java is nine lines and declares two methods with no body. FirstFitAllocator is the only class wired in by default today. corpus/parking-lot/curveballs/01-motorbikes-share-a-spot adds a second implementation, SharedMotorbikeAllocator, at the cost of one changed line in ParkingLot's convenience constructor: the line that names which allocator to build, not a change to ParkingLot's own logic. That is exactly what a realization arrow, drawn now, predicts. A plain association arrow from ParkingLot to SpotAllocator would have been true too, but it would not have told a reviewer which box to watch for a second implementation.
--- Step 5 (0:55) - multiplicities: only where the count is not one
ParkingLot "1" *-- "0..*" Stay
Stay "1" --> "1" Ticket
Stay "1" --> "1" Vehicle
ParkingLot stores open stays in openStays, a Map<String, Stay> with no upper bound — 0..* is the honest count. Stay itself holds exactly one Ticket and one Vehicle, checked in its compact constructor with Objects.requireNonNull, so writing "1" on those two arrows would say nothing a reader could not already assume. Multiplicity earns its place only on the one relationship in this diagram where the count genuinely varies.
What the finished diagram would have missed, drawn at step 2
At step 2, every one of ParkingLot's three collaborators looked the same: a box, a plain arrow. A reviewer asking "what does shutdown need to clean up" gets no answer from that version. By step 5, the same question has one: SpotGrid and every open Stay, because composition says so; nothing about SpotAllocator or PricingPolicy, because association says so. The five extra seconds per arrow bought a diagram that answers a question nobody had to ask out loud.
Worked source
The 1 file of the worked design
Every file below is the one the app opens, verbatim. This is the part worth reading slowly: the prose above argues for a shape, and these are the lines that have it.
worked/Main.java113 lines
worked/Main.java113 lines
/**
* Class Diagram Under Time Pressure: corpus/parking-lot
*
* This program prints the same diagram idea.md builds, one requirement at a time. See
* NOTES.md for the annotated version of this output, tied back to the real files in
* corpus/parking-lot/reference/src.
*/
public class Main {
static class Section {
String title;
String[] lines;
Section(String title, String... lines) {
this.title = title;
this.lines = lines;
}
void print() {
System.out.println("\n--- " + title);
for (String line : lines) {
System.out.println(" " + line);
}
}
}
public static void main(String[] args) {
System.out.println("Class Diagram Under Time Pressure: corpus/parking-lot");
System.out.println("=".repeat(55));
new Section(
"Step 1 (0:15) - boxes only",
"+--------------+ +-----------+ +---------------+ +--------------+",
"| ParkingLot | | SpotGrid | | SpotAllocator | | PricingPolicy|",
"+--------------+ +-----------+ +---------------+ +--------------+",
"",
"Four rectangles, no lines yet. Correct, and it answers nothing an",
"interviewer would ask next."
).print();
new Section(
"Step 2 (0:25) - association: a plain line for \"holds a reference to\"",
"ParkingLot ---> SpotGrid",
"ParkingLot ---> SpotAllocator",
"ParkingLot ---> PricingPolicy",
"",
"Now it says ParkingLot knows about the other three. It does not yet say",
"whether deleting ParkingLot deletes them too."
).print();
new Section(
"Step 3 (0:35) - composition vs association: who owns whom",
"ParkingLot \"1\" *-- \"1\" SpotGrid (composition: built inside the constructor)",
"ParkingLot \"1\" --> \"1\" SpotAllocator (association: injected, may outlive the lot)",
"ParkingLot \"1\" --> \"1\" PricingPolicy (association: injected, may outlive the lot)",
"",
"The filled diamond on SpotGrid says its lifetime is bound to the lot's.",
"The plain arrows on the other two say the opposite: a second ParkingLot",
"could be handed the same allocator instance."
).print();
new Section(
"Step 4 (0:45) - realization: SpotAllocator is an interface",
"FirstFitAllocator ..|> SpotAllocator",
"FlatHourlyPricing ..|> PricingPolicy",
"",
"A dashed line and a hollow triangle mean \"implements\", not \"holds a",
"reference to\". This is the box most likely to grow a second",
"implementation before the round ends -- see lessons/B1."
).print();
new Section(
"Step 5 (0:55) - multiplicities: only where the count is not one",
"ParkingLot \"1\" *-- \"0..*\" Stay",
"Stay \"1\" --> \"1\" Ticket",
"Stay \"1\" --> \"1\" Vehicle",
"",
"A lot holds zero or more open stays at once. A stay holds exactly one",
"ticket and one vehicle, so those ends carry no multiplicity at all --",
"writing \"1\" on every arrow would be noise, not information."
).print();
new Section(
"The finished diagram (1:00) - every arrow from steps 3 to 5, together",
"+------------+",
"| ParkingLot |",
"+------------+",
" | \"1\" *-- \"1\" SpotGrid (composition: built inside the constructor)",
" | \"1\" --> \"1\" SpotAllocator (association: injected, may outlive the lot)",
" | \"1\" --> \"1\" PricingPolicy (association: injected, may outlive the lot)",
" | \"1\" *-- \"0..*\" Stay (composition: dies with the lot)",
"",
"FirstFitAllocator ..|> SpotAllocator (realization: dashed line, hollow triangle)",
"FlatHourlyPricing ..|> PricingPolicy (realization: dashed line, hollow triangle)",
"",
"Stay \"1\" --> \"1\" Ticket (association: no multiplicity needed at 1-to-1)",
"Stay \"1\" --> \"1\" Vehicle (association: no multiplicity needed at 1-to-1)"
).print();
new Section(
"What this diagram leaves out on purpose",
"- no +public/-private marks on any field or method",
"- no full method signatures inside a box",
"- no separate dependency arrows (association already covers \"uses\")",
"- no notes, stereotypes, or <<interface>> banners",
"- no sequence, state, activity, or package diagrams"
).print();
System.out.println("\n" + "=".repeat(55));
System.out.println("Five boxes, three kinds of arrow, multiplicities where the count");
System.out.println("is not one. That is the whole subset a 50-minute round needs.");
}
}
The faded stage is not here, on purpose
In the app, the third stage of this lesson is a written completion: a prompt with blanks, answered in prose and then graded against what the reference extraction expects. The grading needs the app, so this page stops at the worked walkthrough.
Run the app for the drill: it is the download in the header, and it works offline once unpacked.