Production LLD
Testing an LLD design
You finished park and exit on the whiteboard. The interviewer asks how you would test this Low-Level Design (class-level design on the whiteboard). If the answer is "I would run main and type," you have no proof the second gate cannot steal a spot.
Testing LLD means proving the design with focused tests: happy paths, edge cases, and concurrency where shared state exists. Those tests read like the requirements: park a car, reject a full lot, freeze two hours, check the fee. Drive the . Assert an .
What actually happens when you only run main
The typed demo in the hero only proves the path you just drew. It does not reject a full lot or freeze two hours for a fee. Those requirements become named tests in the matrix below, not extra commentary after a Scanner loop (keyboard input into main).
A useful suite is small and reads like the spec you were given. Name each test after the requirement. One scenario per test: a method named test1 that asserts true is not documentation. The next section is the shape of one such test; the layers after that decide how many suites you bother to write.
The shape of one unit test
The story above says what to prove. is how one test proves it: set up a lot you control, call the , and check a ticket, a receipt, or an exception.
Arrange
Lot with one spot, nearest allocation, hourly price, and Clock.fixed(...) (a clock stuck on one instant). Fake TicketRepository if persistence is a port (an interface the domain owns).
Act
Call park(Vehicle) or exit(TicketId). Never a UI loop, never Thread.sleep to wait for a fee.
Assert
Receipt amount, ticket status, or exception type. Query isFull() or ticket(id). Reflection (reading private fields at runtime) on spots means the API is missing a query.
Simple rule of thumb
Name tests after the requirement. One scenario per test. Arrange with a frozen Clock (see Arrange above). Act through the service. Assert a ticket, a receipt, or an exception. Do not assert private occupancy maps (see Assert above).
Layers you actually need
Arrange-act-assert is one test. These bars are how much suite you write at each layer, from use cases through value objects (compared by value, not identity) down to adapters (port implementations) and UI. In an interview, the top bar is the one that matters: use-case tests through ParkingService.
Use-case tests through ParkingService
This is the suite that matters in a forty-five minute interview.
Value-object tests (Money, TicketId)
Reject negative amounts; compare Money and TicketId by value, not identity.
Adapter tests (optional)
Skip these adapter tests unless persistence itself is the problem.
UI / Scanner
Do not test Scanner or UI; keep both out of the domain.
Requirement matrix
Tests that read like the spec
The layers above say ParkingService is the suite that matters. Here is what that suite looks like as names, then as Java. Each row is one requirement, one test name, and one assert.
| Requirement | Test name | Assert |
|---|---|---|
| Park a car | issuesTicketWhenSpotFree | ticket status OPEN, spot taken |
| Reject a full lot | cannotParkWhenLotIsFull | LotFullException |
| Fee after two hours | airportFeeAfterFirstHour | Money equals tariff |
| No double occupancy | cannotIssueTwoTicketsForOneSpot | second park fails |
| Closed stays closed | cannotReactivateClosedTicket | exception or no-op |
| Two gates | twoGatesCannotTakeSameSpot | one success, one reject |
A test that reads like the spec
with a frozen Clock. Time is a value you control. Sleeping until midnight is not a test.
@Test
void cannotIssueTwoTicketsForOneSpot() {
Clock clock = Clock.fixed(Instant.parse("2026-01-15T10:00:00Z"), ZoneOffset.UTC);
ParkingLot lot = new ParkingLot(oneSpot(), nearest(), hourly(Money.rupees(10)), clock);
Ticket first = lot.park(car("KA-01"));
assertThrows(LotFullException.class, () -> lot.park(car("KA-02")));
assertEquals(first.spotId(), lot.ticket(first.id()).spotId());
}
@Test
void airportFeeAfterTwoHours() {
MutableClock clock = MutableClock.at("2026-01-15T10:00:00Z");
ParkingLot lot = new ParkingLot(oneSpot(), nearest(), airportTariff(), clock);
Ticket ticket = lot.park(car("KA-01"));
clock.advance(Duration.ofHours(2));
Receipt receipt = lot.exit(ticket.id());
assertEquals(Money.rupees(80), receipt.amount());
}What not to write
@Test
void test1() {
ParkingLot lot = new ParkingLot(); // real now(), real Random, Scanner
lot.mainLoop(); // types into stdin
assertTrue(lot.spots.get(0).occupied); // private field
}You cannot assert a with stdin (typed input). Keep IO out of the domain so tests can exist. The next refactor that changes a private map should not break a product that still parks correctly.
Forty-five minutes
How to explain this in an interview
You will not type the whole matrix in forty-five minutes. Two named tests plus a spoken list still scores; zero tests and a God object (one class that knows everything) does not.
0–30
Working park/exit
Domain first; tests wait until the park and exit API exists.
30–38
Two tests
Write a happy path plus the invariant they asked about.
38–42
Speak the rest
Speak illegal transitions, a race, and the money edge.
42–45
Clock and fakes
Show how you would freeze time so a fee test does not wait.
You may only have time to write cannotParkWhenLotIsFull and airportFeeAfterTwoHours. Say the others. Mention Clock.fixed so the fee test is not a midnight flake (a failure only when the clock rolls over). Mention that a test would use two threads or a latch (a barrier that starts both parks together) if you claimed thread safety. That spoken list is part of the design, not leftover commentary.
Write
Happy path plus the they asked about (full lot, unique spot, fee).
Say
Illegal transitions, a closed ticket, two gates, and a negative Money constructor.
Common mistakes
Testing through main and Scanner
See What not to write above: main and Scanner are not a suite.
Asserting private occupancy maps
See Assert above: query isFull() or ticket status, not a private map.
Coverage of getters
Getter coverage such as assertEquals(ticket.getPlate(), "KA-01") teaches nothing; illegal states and fees lock the design.
Wrap-up
You have seen the failure of running main, the shape of one test, and what still scores in forty-five minutes. Check the ideas stuck, then follow the related notes.
Check yourself
- Write the test name for "EV cannot park in a compact-only lot." What do you assert?
- A colleague tests occupancy via reflection. Which query should exist instead?
- How do you prove a two-hour airport fee without waiting two hours?
Key takeaway
Name tests after requirements. Two solid tests through the beat a God object with none.