Learning pathsPricingChecking sign-in…
← LLD problem library

LLD-001 · DESIGN PRACTICE

Parking Lot

From a simple spot scan to atomic allocation, immutable ticket terms and safe duplicate operations.

Local Java 21 reference verified: 30 checks. Learner drafts are not executed. Author self-review; independent review pending.

Existing introductory lesson · Version 1

Understand the problem

Design a parking facility with several entry and exit gates. Allocate a compatible spot, issue a ticket, calculate a fee, record a trusted payment receipt and release occupancy only when the vehicle exits. Explain which object owns each invariant and why a thread-safe map alone cannot make allocation safe.

Before you start

  • Objects and responsibilities
  • SOLID and composition
  • Concurrency and correctness

Scope and explicit decisions

  • One process and one facility. No database, hardware gate, license-plate recognition or payment-provider network calls are implemented.
  • Amounts are nonnegative integer minor units in one facility currency. Pricing rounds positive partial hours up; zero elapsed time costs zero. No floating-point arithmetic.
  • SMALL vehicles can use either spot size; LARGE vehicles require LARGE. First compatible spot in configured order wins. This simple rule may consume a large spot that a later large vehicle needs.
  • The trusted caller supplies nonnegative elapsed seconds from one clock origin. An earlier quote time is rejected. Billing-clock selection is an adapter responsibility.
  • A ticket captures the immutable Rate at entry. Rate changes affect new tickets only. A quote is immutable; paying any issued quote freezes that charge. There is no post-payment grace deadline in this teaching contract.
  • The payment boundary is trusted: receipt strings are deduplication identifiers, not proof of a genuine payment. A production adapter must authenticate and validate currency/amount against the quote before calling recordPayment.
  • Operation IDs are facility-scoped and never reused. Retrying park returns the same ticket identity with its current state. Reusing a key for a different vehicle is rejected.
  • Parking records, quotes, receipts and idempotency results remain in memory for this reference. Storage is unbounded over lifetime; restart loses all state. Retention and durability are explicitly Staff design extensions.

Worked examples

Input: One LARGE spot. Vehicle A enters at 0; quote at 3600; rate 100/hour.

Outcome: One OPEN ticket; quote = 100. Payment → PAID with occupancy 1. Exit → CLOSED with occupancy 0.

Billing and physical allocation have different completion points.

Input: A enters at rate v1=100. Install v2=200. A leaves after one hour; B enters under v2.

Outcome: A quote stays 100; B one-hour quote is 200.

Historical behavior follows captured policy, not current configuration.

Your design attempt

Each language has a separate draft across all three levels. Saving is device-only and does not sync to your account. Anyone using this browser profile can load it. Loading is always explicit.

Java 21 reference code is provided for local study. Use Run program in the editor to execute your code and test driver. Program runs show output; they do not grade your design.

Solution.java
Run program · Java 21

No unsaved changes · 187/20000

Self-review

Checks apply to this visit. Self-review is not a passed submission or verified mastery.

Try before revealing

Hint 1

Which operation must create both occupancy and ticket ownership together?

Hint 2

Draw OPEN, PAID and CLOSED. Which transition actually means the vehicle has left?

Hint 3

Keep immutable pricing on the ticket and a command-result ledger inside the same atomic boundary.

Requirements and invariants

R1 — At most one active ticket owns any compatible spot. park protects I1.

R2 — Payment changes ticket state but not occupancy. recordPayment protects I2.

R3 — Only paid tickets can exit; repeated old exits cannot release a new occupant. exit protects I1.

R4 — Existing tickets retain their entry pricing version. changeRate protects I3.

I1: Every OPEN/PAID ticket has exactly one occupied spot and one active vehicle entry, and vice versa.

I2: Allowed lifecycle: OPEN → PAID → CLOSED. Only CLOSED releases the spot.

I3: Ticket.rate never changes after creation, even if currentRate changes.

Derive the design

A correct simple scan, then one explicit synchronization boundary

Start with a single-threaded list scan: choose a compatible free spot, create a ticket, mark occupancy. For one command at a time this is a complete allocation mechanism. Do not optimize before identifying the invariant. The reference keeps the scan and adds synchronized service operations, preserving a clear atomic check-and-update. A ConcurrentHashMap only makes each map call safe; the decision spans several maps. A first-fit scan is not a broken baseline—it is an intentional O(S) choice for a small facility.

Alternative: If measured allocation latency is dominated by scanning, maintain free-spot indexes by compatibility class. Removing an index entry and creating a ticket must still share the same transaction/lock. Per-floor locks increase parallelism but cross-floor selection and migration introduce ordering concerns.

Identify the hard invariant before naming classes

Vehicle and Spot are nouns, but the crucial behavior is a single ownership decision. Ticket cannot safely claim a spot independently of the allocation service. Keep a single authoritative map relationship, then expose immutable snapshots.

Separate payment acknowledgement from physical exit

A vehicle may remain inside after paying. Releasing on payment would permit a new ticket for an occupied spot. OPEN→PAID preserves occupancy; PAID→CLOSED releases it. A hardware gate needs a separate authenticated event contract in a real implementation.

Choose a lock that covers the real transaction

One monitor provides visibility and mutual exclusion for selection, ticket creation and duplicate recording. A synchronized getter is also needed for consistent reads. Only after measuring contention should this become indexed allocation or per-floor locking.

Decide retries at the command boundary

Store enough of the request to detect conflicting key reuse. Here immutable Vehicle equality is the fingerprint; at is treated as observed execution time, not part of request identity. Specify this distinction so a retry does not fail merely because its clock is later.

Responsibilities and ownership

ParkingLot

Owns: Spot ownership, ticket lifecycle, operation and receipt ledgers

Methods: park, quote, recordPayment, exit

One monitor owns every compound state change; it performs no network I/O.

Vehicle / Spot

Owns: Validated identity and compatibility class

Methods: Immutable Java record accessors

Do not own allocation or payment.

Ticket

Owns: Entry time, captured rate, vehicle/spot identity and lifecycle snapshot

Methods: Immutable accessors; service replaces snapshot

Callers cannot set state directly.

Rate / Quote

Owns: Versioned price rule / immutable computed amount

Methods: Validated constructors and accessors

Rate is a concrete immutable policy value, not an arbitrary callback under the lock.

Ownership and call direction

The monitor encloses only local state changes. Provider I/O is outside this model.

  1. Gate / trusted adapter
  2. ↓ Gate invokes park / payment / exit

    ParkingLot monitor
  3. ↓ One monitor commits all related maps

    Occupancy + ticket maps
  4. ↓ Caller receives immutable state

    Immutable ticket snapshot
Ticket lifecycle

OPEN cannot exit. Repeated payment/exit resolves the existing identity; CLOSED never releases occupancy again.

  1. OPEN: owns spot
  2. ↓ Trusted receipt for stored quote

    PAID: owns spot
  3. ↓ Confirmed physical exit

    CLOSED: releases spot

Data model, schema and relationships

Parking identity is separate from physical occupancy, ticket lifecycle and payment evidence. A vehicle may visit many times; a spot is reused over time, so historical tickets cannot uniquely own it forever.

Implementation boundary: The reference uses local Spot/Ticket records and maps. These tables are a proposed persistence design; vehicle/payment rows expand the adapter boundary rather than claim a database already exists.

These are proposed logical schemas with field contracts, not executable database migrations. PK identifies a row, FK references an existing parent, UK is a uniqueness rule. A composite PK identifies the combination. Nullable references are marked with ?. A foreign key protects identity; cross-row limits, timing and state transitions also require the transaction rules below.

vehicle: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

vehicle relationshipsvehicle 1 to ticket 0..*: makes visits. ticket.vehicle_id → vehicle.id.makes visits1 parent · 0..* childrenticketid [PK]vehicle_id [FK]spot_id [FK]vehicleid [PK]platekind
spot: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

spot relationshipsspot 1 to ticket 0..*: hosts visits. ticket.spot_id → spot.id.hosts visits1 parent · 0..* childrenticketid [PK]vehicle_id [FK]spot_id [FK]spotid [PK]kindversion
ticket: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

ticket relationshipsticket 1 to payment 0..*: has settlement evidence. payment.ticket_id → ticket.id.has settlement evidence1 parent · 0..* childrenpaymentid [PK]ticket_id [FK]amountticketid [PK]vehicle_id [FK]spot_id [FK]

Entity definitions and field contracts

vehicle

Stable vehicle identity across visits.

FieldType / keyMeaning and reference
iduuid · PKVehicle identity
platetext · UKNormalized registration within issuing jurisdiction
kindenum · VALUECompatibility class
jurisdictiontext · VALUEPlate issuing authority used in normalized uniqueness

Code mapping: Vehicle compatibility input.

  • Unique (jurisdiction,normalized_plate) in a real multi-region system; plate alone is not globally unique.

spot

Physical capacity owned by one facility.

FieldType / keyMeaning and reference
iduuid · PKSpot identity
kindenum · VALUESupported vehicle class
versionbigint · VALUEConditional allocation version

Code mapping: Spot record and occupancy map.

  • Exactly one current occupancy per spot; historical ticket references are allowed.

ticket

One visit with snapshotted pricing policy.

FieldType / keyMeaning and reference
iduuid · PKVisit identity
vehicle_iduuid · FKVisiting vehicle · vehicle.id
spot_iduuid · FKAllocated spot · spot.id
stateenum · VALUEOPEN or CLOSED
policy_versiontext · VALUEOriginal pricing interpretation
created_attimestamp · VALUEImmutable acceptance instant for history queries

Code mapping: Ticket and atomic park/leave.

  • Unique active ticket per vehicle and spot, enforced by partial unique indexes or explicit occupancy rows.

payment

Trusted correlated payment evidence.

FieldType / keyMeaning and reference
idtext · PKProvider operation identity
ticket_iduuid · FKVisit being settled · ticket.id
amountbigint · VALUEExact minor units
statusenum · VALUEKnown or unknown provider result
currencytext · VALUECurrency must match ticket pricing contract

Code mapping: Payment adapter extension; reference trusts payment input.

  • Provider identity unique; validate amount/currency and ticket association before release.

Relationship cardinalities and their reasons

Parent → childMultiplicityFK / why
vehicle → ticket

makes visits

1 parent per child; 0..* children per parentticket.vehicle_id → vehicle.id

One vehicle has many historical visits; active-visit uniqueness is a separate constraint.

spot → ticket

hosts visits

1 parent per child; 0..* children per parentticket.spot_id → spot.id

A spot has many historical tickets, but at most one active owner.

ticket → payment

has settlement evidence

1 parent per child; 0..* children per parentpayment.ticket_id → ticket.id

Multiple attempts may exist; one accepted settlement must not free a spot twice.

Transaction boundaries

  1. park: lock/conditionally claim compatible spot, enforce vehicle active uniqueness and insert ticket in one transaction.
  2. leave: correlate accepted settlement with the same ticket, close it and release its current occupancy atomically. Provider I/O occurs outside database locks.

Indexes and uniqueness

  1. Unique active (spot_id) and active (vehicle_id); ordinary historical FKs stay nonunique.
  2. Index ticket(vehicle_id,created_at) for visit history and payment(ticket_id,status) for settlement lookup.

Schema traps and failure cases

  1. A stale ticket cannot release a spot now owned by another visit.
  2. Unknown payment is retained for reconciliation; it is not definite failure.

Trace the records through one operation

  1. Vehicle V opens T1 in S1; active uniqueness blocks a second owner.
  2. Settlement P1 closes T1 and frees S1 in one commit.
  3. V returns under T2; replaying T1/P1 cannot release T2.

Method contracts

park(Vehicle, operationId, at) → Ticket

Validates input, finds first compatible spot, claims it and records ticket/result under one monitor. Full/already parked are rejected without mutation. Retry key must match the vehicle; the later retry time is not a new business request.

quote(ticketId, at) → Quote

OPEN only. Reject clock reversal. Use checked multiplication; overflow fails before quote storage. Quote creation does not charge money.

recordPayment(quoteId, receiptId) → Ticket

Trusted adapter only. OPEN → PAID. Same receipt+quote is a no-op returning current ticket; receipt reused for another quote fails.

exit(ticketId) → Ticket

PAID → CLOSED with occupancy release. OPEN fails; CLOSED is a no-op, including after the spot has a new occupant.

changeRate(Rate) → void

Replaces the current entry policy under the same monitor. Existing records retain their Rate object.

What, when, why and how

Each concept is attached to the requirement it protects, its implementation, an alternative and a test.

Encapsulation and immutable snapshots

what
Put state transitions behind methods and return immutable records.
when
When callers could otherwise edit a status, occupied flag or rate.
why
A valid ticket and its occupancy must change together; a public status setter bypasses that invariant.
how
ParkingLot keeps private maps. withState returns a new Ticket. The caller sees a snapshot; later payment does not mutate an older Ticket reference.

Code: withState, ticket · Invariants: I1, I2

Alternatives, cost and failure example

Alternative: Public mutable DTOs are simpler only for unshared input, not authoritative domain state.

Cost: New snapshots allocate objects; records retain referenced values, so all nested values here are immutable.

Counterexample: Caller changes ticket to CLOSED but the spot remains occupied: a public setter creates contradictory truth.

Test: Attempt illegal exit and assert state/occupancy are unchanged; snapshot rate remains v1.

Atomic aggregate update

what
Protect the entire read-decide-write operation, not individual fields.
when
Two entry gates compete for the final compatible spot.
why
Both callers can read free before either writes, producing two active tickets for one spot.
how
synchronized park selects the spot, updates occupied/tickets/activeVehicles and stores the operation result while holding the same monitor. All mutating readers use that monitor too.

Code: park, invariantHolds · Invariants: I1

Alternatives, cost and failure example

Alternative: One service event loop can also serialize commands. A database transaction is required when owners span processes.

Cost: All facility operations serialize. The lock includes only bounded in-memory work, never provider I/O.

Counterexample: Check occupied outside the monitor then write inside: both gates can pass the stale check.

Test: Release two workers from a CountDownLatch; assert exactly one winner and the map/ticket invariant.

Money and versioned pricing policy

what
Represent amounts exactly and bind a policy version to the business record.
when
Fees must remain explainable after price changes.
why
Re-reading currentRate for an old ticket silently changes the agreement made at entry.
how
Rate is an immutable version and minorUnitsPerHour. Ticket captures it. quote computes ceiling elapsed hours and Math.multiplyExact; a zero-duration visit costs zero.

Code: Rate, quote, changeRate · Invariants: I3

Alternatives, cost and failure example

Alternative: For variable tariffs introduce a pure PricingPolicy outside network boundaries, with immutable versioned inputs. Do not add a Strategy interface solely to increase class count.

Cost: Whole-hour rounding is a business rule, not numerical necessity; complex tariffs need more metadata and tests.

Counterexample: At 3601 seconds, truncating elapsed/3600 undercharges by one hour; using doubles risks exact receipt comparison.

Test: Test 0, 3600 and 3601 seconds, overflow, and v1 ticket after v2 rollout.

Idempotent command identity

what
A retry resolves the original logical operation instead of performing it again.
when
Gate/client times out after an allocation or payment was accepted.
why
A retry must not allocate another ticket or release another car’s reused spot.
how
Store operationId→vehicle+ticket and receiptId→quote. Reject conflicting reuse. exit uses the CLOSED ticket as its natural deduplication state.

Code: park, recordPayment, exit · Invariants: I1, I2

Alternatives, cost and failure example

Alternative: If durable storage is introduced, put the dedupe record and business mutation in the same transaction. A separate cache can lose the atomic relationship.

Cost: Retained results grow over time; retention expiry must not allow ancient retries to become new commands.

Counterexample: Delete a closed ticket then retry old exit after S1 is reallocated: naïve release-by-spot can evict the new owner.

Test: Repeat payment and exit; reallocate S1; retry old exit and assert new occupancy remains.

Read and run the Java reference

Reveal complete Solution.java

Java 21. Save as Solution.java; compile with javac --release 21. The repository also contains Test.java and the lld:java verification command. This reference implements the local contract above.

Solution.java Java 21
import java.util.*;

/** One-process teaching reference. No provider I/O, persistence or gate hardware. */
public final class Solution {
    private Solution() {}
    public enum Size { SMALL, LARGE }
    public enum State { OPEN, PAID, CLOSED }
    public record Vehicle(String id, Size size) {
        public Vehicle { requireId(id); Objects.requireNonNull(size); }
    }
    public record Spot(String id, Size size) {
        public Spot { requireId(id); Objects.requireNonNull(size); }
    }
    public record Rate(String version, long minorUnitsPerHour) {
        public Rate { requireId(version); if (minorUnitsPerHour <= 0) throw new IllegalArgumentException("rate"); }
    }
    public record Ticket(String id, Vehicle vehicle, String spotId, long enteredAt,
                         Rate rate, State state) {}
    public record Quote(String id, String ticketId, long at, long amount, String rateVersion) {}
    private record ParkResult(Vehicle vehicle, String ticketId) {}
    private static void requireId(String value) {
        if (value == null || value.isBlank()) throw new IllegalArgumentException("empty identifier");
    }
    public static final class ParkingLot {
        private final List<Spot> spots;
        private final Map<String, String> occupied = new HashMap<>();
        private final Map<String, String> activeVehicles = new HashMap<>();
        private final Map<String, Ticket> tickets = new HashMap<>();
        private final Map<String, ParkResult> operations = new HashMap<>();
        private final Map<String, Quote> quotes = new HashMap<>();
        private final Map<String, String> receipts = new HashMap<>();
        private Rate currentRate;
        private long sequence;
        public ParkingLot(List<Spot> spots, Rate rate) {
            this.spots = List.copyOf(spots); this.currentRate = Objects.requireNonNull(rate);
            if (this.spots.stream().map(Spot::id).distinct().count() != this.spots.size())
                throw new IllegalArgumentException("duplicate spot");
        }
        // Linearization boundary: selection, claim and ticket creation share this monitor.
        public synchronized Ticket park(Vehicle vehicle, String operationId, long at) {
            Objects.requireNonNull(vehicle); requireId(operationId);
            if (at < 0) throw new IllegalArgumentException("time");
            ParkResult previous = operations.get(operationId);
            if (previous != null) {
                if (!previous.vehicle().equals(vehicle)) throw new IllegalArgumentException("key reused");
                return tickets.get(previous.ticketId()); // Current snapshot of same ticket identity.
            }
            if (activeVehicles.containsKey(vehicle.id())) throw new IllegalStateException("already parked");
            Spot selected = spots.stream().filter(s -> !occupied.containsKey(s.id()) &&
                (s.size() == Size.LARGE || vehicle.size() == Size.SMALL)).findFirst()
                .orElseThrow(() -> new IllegalStateException("full"));
            String id = "T" + (++sequence);
            Ticket ticket = new Ticket(id, vehicle, selected.id(), at, currentRate, State.OPEN);
            occupied.put(selected.id(), id); tickets.put(id, ticket);
            activeVehicles.put(vehicle.id(), id); operations.put(operationId, new ParkResult(vehicle, id));
            return ticket;
        }
        public synchronized Quote quote(String ticketId, long at) {
            Ticket t = ticket(ticketId);
            if (t.state() != State.OPEN) throw new IllegalStateException("not open");
            if (at < t.enteredAt()) throw new IllegalArgumentException("clock reversal");
            long elapsed = Math.subtractExact(at, t.enteredAt());
            long hours = elapsed / 3600 + (elapsed % 3600 == 0 ? 0 : 1);
            long amount = Math.multiplyExact(hours, t.rate().minorUnitsPerHour());
            Quote q = new Quote("Q" + (++sequence), ticketId, at, amount, t.rate().version());
            quotes.put(q.id(), q); return q;
        }
        // Caller is a trusted payment adapter. A receipt string is not payment authentication.
        public synchronized Ticket recordPayment(String quoteId, String receiptId) {
            requireId(receiptId);
            Quote q = quotes.get(quoteId);
            if (q == null) throw new IllegalArgumentException("unknown quote");
            String prior = receipts.get(receiptId);
            if (prior != null) {
                if (!prior.equals(quoteId)) throw new IllegalArgumentException("receipt reused");
                return ticket(q.ticketId());
            }
            Ticket t = ticket(q.ticketId());
            if (t.state() != State.OPEN) throw new IllegalStateException("not open");
            Ticket paid = withState(t, State.PAID); tickets.put(t.id(), paid);
            receipts.put(receiptId, quoteId); return paid;
        }
        // Ticket identity makes repeated exit naturally idempotent, even after spot reuse.
        public synchronized Ticket exit(String ticketId) {
            Ticket t = ticket(ticketId);
            if (t.state() == State.CLOSED) return t;
            if (t.state() != State.PAID) throw new IllegalStateException("unpaid");
            Ticket closed = withState(t, State.CLOSED);
            occupied.remove(t.spotId()); activeVehicles.remove(t.vehicle().id());
            tickets.put(t.id(), closed); return closed;
        }
        public synchronized void changeRate(Rate rate) { currentRate = Objects.requireNonNull(rate); }
        public synchronized Ticket ticket(String id) {
            Ticket t = tickets.get(id); if (t == null) throw new IllegalArgumentException("unknown ticket");
            return t;
        }
        public synchronized int occupiedCount() { return occupied.size(); }
        public synchronized boolean invariantHolds() {
            long active = tickets.values().stream().filter(t -> t.state() != State.CLOSED).count();
            return active == occupied.size() && active == activeVehicles.size() &&
                tickets.values().stream().filter(t -> t.state() != State.CLOSED).allMatch(t ->
                    t.id().equals(occupied.get(t.spotId())) && t.id().equals(activeVehicles.get(t.vehicle().id())));
        }
        private static Ticket withState(Ticket t, State state) {
            return new Ticket(t.id(), t.vehicle(), t.spotId(), t.enteredAt(), t.rate(), state);
        }
    }
}

park: retry before allocation

Resolve an existing operation first, then reject another active ticket for the vehicle. Select a compatible spot while still owning the monitor; commit all maps before returning. No caller can observe a partial committed state through the public methods.

quote: calculate before storing

Validate state and time, derive ceil-hours without adding 3599 (which could overflow), then multiply with overflow checking. Only after a valid amount exists do we allocate/store the Quote.

recordPayment and exit: monotonic state

Receipt identity prevents another mutation on retry. An old CLOSED ticket exits as a no-op before touching occupancy. This order is why stale exits cannot free a new owner’s spot.

changeRate: immutable policy replacement

New tickets capture the new Rate. Existing Tickets point to their earlier immutable Rate, so no data migration is needed for this particular change.

Complexity and limits

  • park scans S spots: O(S) expected map lookup work; ticket/receipt lookup and lifecycle changes are expected O(1). Hash lookup is not an unconditional worst-case guarantee.
  • quote is O(1) for fixed-width arithmetic; checked multiplication throws on overflow. invariantHolds is O(T) and is a diagnostic/test operation, not a hot-path necessity.
  • Configured spots use O(S); retained tickets T, quotes Q, operations O and receipts R use O(T+Q+O+R). This example has no retention cleanup or durability.
  • One monitor serializes all commands. O(1) data access does not imply bounded latency under contention; queueing grows with arrival rate.

Walk through the actual reference

These walkthrough fixtures are emitted only after the Java harness verifies their operations and expected state. Concurrency tests additionally exercise competing callers.

Last spot, payment and physical exit

Step 1 of 5: A parks

S1 owned by A; ticket OPEN

Invariant: 1 active ticket = 1 occupied spot

Read the complete trace table
ActionState after actionInvariant
A parksS1 owned by A; ticket OPEN1 active ticket = 1 occupied spot
B attempts the last spotRejected: full; A still owns S1No double allocation
Quote at 3600 seconds100 minor units at v1Ticket retains its original rate
Record trusted payment receiptTicket PAID; S1 remains occupiedPayment does not release physical occupancy
Confirm exitTicket CLOSED; S1 free0 active tickets = 0 occupied spots

Mistakes and counterexamples

Use a concurrent map and assume the whole operation is safe

Failing case: Gate A and B both observe S1 free before either inserts.

Repair: Synchronize selection plus all ownership writes and idempotency recording.

Release the spot when the payment succeeds

Failing case: A pays but has not driven out; B receives A’s occupied spot.

Repair: Keep PAID active; release only on confirmed exit.

Calculate old ticket prices from current configuration

Failing case: v2 doubles the hourly price while A is already inside.

Repair: Capture an immutable policy on ticket creation and keep it in quotes.

Treat a fake receipt string as proof of payment

Failing case: An untrusted client invents receipt-123 and calls recordPayment.

Repair: Authenticate provider events and compare amount/currency in a trusted adapter before this domain API.

Engineer → Senior → Staff

Choose a deep dive

Derive a small, working object model.

Executed: first-fit compatible allocation, immutable values, rounding and valid lifecycle.

Change exercise

Add a SMALL vehicle and two differently sized spots; explain allocation order and write the zero-duration pricing test.

Worked answer guidance

First-fit is deterministic but not future-optimal. Preserve narrow domain responsibilities and show why 0 elapsed seconds produces a zero quote under this contract.

Interview follow-ups

Why is Ticket a record rather than a public mutable class?

It gives callers a snapshot while ParkingLot owns transitions. A record alone is not deep immutability if nested values are mutable; these nested values are records/enums.

Why not create a subclass for every kind of vehicle?

Compatibility is a small value-based rule. A hierarchy is only justified by genuinely different substitutable behavior, not by nouns.

Return to the lesson →

Provenance and scope of evidence

Original teaching material. Local semantics are defined by this chapter and executable Solution.java/Test.java. Java 21 language/runtime is pinned by the verification harness.

Reference verification is not independent editorial review, production deployment verification or a claim that distributed adapters are implemented.