Learning pathsPricingChecking sign-in…
← LLD problem library

LLD-008 · LOW LEVEL DESIGN

Movie Ticket Booking

Protect scarce seats with atomic holds, deadline checks and explicit late-payment repair.

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

Existing introductory lesson · Version 1

Understand the problem

Design seat reservation for several shows. Hold a group atomically for one owner until a deadline, confirm a trusted successful payment, and handle expiry, cancellation and duplicate callbacks without selling the same seat twice. Explain why payment success cannot overrule ownership that has already expired.

Before you start

  • Atomic compound updates
  • Leases and authoritative deadlines
  • Idempotency fingerprints
  • Money versus inventory outcomes

Scope and explicit decisions

  • In-memory inventory across configured shows and named seats. Each seat costs one configured positive minor-unit price; show-seat identity matters. Search, seat-map rendering, real payment processing, authentication, durable storage and a waiting room are not implemented.
  • A hold is all-or-nothing over a nonempty seat set. HELD and CONFIRMED both own their seats. A HELD record expires when expiresAt<=trusted now. CONFIRMED ownership does not expire.
  • Time is a nonnegative, nondecreasing elapsed-unit value supplied by a trusted adapter. New operations and duplicate holds advance time and sweep all expired HELD records. A read through get shows the last processed state and does not itself advance time.
  • TTL and total price use checked arithmetic before allocation. A valid operation that advances time may expire unrelated old holds even if its requested seats remain unavailable; rejection does not undo the passage of time.
  • hold keys are scoped to the inventory and fingerprint show, seat set, owner and TTL. Same key resolves the same hold identity; a retry cannot extend its deadline or create a new hold after expiry. Set iteration order does not change identity.
  • confirm accepts an authenticated completed-payment fact from a trusted adapter, not an unverified browser receipt. Owner and amount must match the stored hold. The implementation does not actually charge or refund money.
  • A first valid payment confirms only a still-HELD record. Payment after EXPIRED/CANCELLED, or another distinct successful payment after CONFIRMED, records REFUND_REQUIRED without changing seat ownership. A matching duplicate payment returns its stored result; conflicting reuse fails.
  • cancel releases an unconfirmed HELD record or returns its existing terminal state. Confirmed booking cancellation requires a separate refund policy/workflow and is rejected here. A refund obligation is a recorded result, not proof the external refund occurred.
  • Hold, command and payment histories remain in memory for the process lifetime. Restart loses ownership and dedupe state; a durable multi-process adapter is a Staff design extension.

Worked examples

Input: Alice holds A,B until t=10. Bob asks for B,C before t=10.

Outcome: Bob is rejected with no claim on C. At t=10 Alice expires and Bob may hold A.

Atomic group allocation and exact deadline semantics are separate invariants.

Input: Alice’s successful charge arrives after Bob owns A.

Outcome: Alice payment records REFUND_REQUIRED; Bob’s ownership is unchanged.

Financial repair is required instead of retroactively winning the seat race.

Try before revealing

Hint 1

What identity owns a seat between selection and confirmed booking?

Hint 2

Does the exact deadline belong in a timer or in every authoritative decision?

Hint 3

What must happen to a successful charge that loses the seat race?

Requirements and invariants

R1 — Allocate every requested show-seat or none, with at most one owner per seat. hold protects I1.

R2 — Expiry and cancellation release only seats still owned by that hold. release protects I2.

R3 — Late or duplicate successful charges cannot acquire another owner’s seats. confirm protects I3.

I1: Each HELD/CONFIRMED seat maps to exactly its hold ID; no seat has two active owners.

I2: Terminal unconfirmed holds own no seats. Conditional release cannot remove a later hold’s ownership.

I3: Each payment ID has one immutable request fingerprint and one stored confirmation/refund outcome.

Derive the design

Availability display is not an allocation decision

A seat-map reader can show which seats appear available, but read-then-write from independent callers permits both to observe the same free seat. Start with a single atomic service decision: validate the whole group and claim it under one owner. The reference adds time-limited holds and immutable IDs before payment integration so scarce ownership remains authoritative.

Alternative: Per-seat locks can permit unrelated seats to progress, but group claims need deterministic lock ordering and shared hold/payment records. A database conditional update or transaction can coordinate multiple processes; a JVM lock cannot. A waiting room reduces pressure but does not replace ownership checks.

Clarify ownership and expiry before payments

Ask about show-seat identity, group atomicity, time authority and exact deadline. A payment API cannot determine correct booking behavior until those contracts are fixed.

Make one local transaction easy to inspect

The monitor encloses expiry, group validation, ownership creation and command-result recording. It never calls a payment provider.

Separate repeat identity from fresh business intent

An expired hold key still identifies that old hold. A new attempt to reserve seats needs a new key; otherwise a retry could silently extend a lease.

Record the losing payment path

A late payment must have an explicit result that can drive refund/review work. Keeping this path visible makes the design maintainable under reordered callbacks.

Responsibilities and ownership

Inventory

Owns: Configured show-seats, ownership, hold history and operation/payment results

Methods: hold, confirm, cancel, ownerOf

One monitor owns allocation, expiry and payment-to-seat decision.

Hold

Owns: Stable identity, immutable seat set/owner/price/deadline and lifecycle snapshot

Methods: Record accessors

Callers cannot extend TTL or set CONFIRMED themselves.

Seat / Request / Payment

Owns: Structured identity and retry arguments

Methods: Immutable record equality

Show-seat keys and set-based fingerprints avoid accidental cross-show conflicts or ordering differences.

Confirmation

Owns: Payment correlation and BOOKED/REFUND_REQUIRED outcome

Methods: Immutable record accessors

A repair obligation is not an external refund acknowledgement.

Reservation boundary

Payment I/O is outside this local model; confirmation input is trusted completed-payment evidence.

  1. Show-seat group + owner + key
  2. ↓ Trusted clock and checked price

    Enforce expiry and validate all seats
  3. ↓ No partial group mutation

    Claim one immutable hold
  4. ↓ Payment success cannot override expiry

    Confirm current ownership or record repair
Late-payment decision

Refund-required is an obligation to an external workflow, not a completed refund.

Fresh successful payment for stored hold

Current exclusive owner

Still HELD: BOOKED

Preserve existing seat ownership

Expired/cancelled/already paid: REFUND_REQUIRED

Data model, schema and relationships

BookMyShow needs a reusable physical seat and a separate per-show seat inventory. Users place orders for a particular show; order lines connect the order to its selected seats. Payment attempts and hold ownership are separate facts.

Implementation boundary: The reference implements per-show atomic holds and correlated payment outcomes in memory. User, movie, theatre and screen catalog tables below explain the surrounding domain; SQL constraints/transactions are a proposed durable extension.

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.

app_user: 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.

app_user relationshipsapp_user 1 to booking_order 0..*: places orders. booking_order.user_id → app_user.id.places orders1 parent · 0..* childrenbooking_orderid [PK]user_id [FK]show_id [FK]app_userid [PK]email
movie: 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.

movie relationshipsmovie 1 to show 0..*: is screened. show.movie_id → movie.id.is screened1 parent · 0..* childrenshowid [PK]movie_id [FK]screen_id [FK]movieid [PK]titleduration_minutes
theatre: 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.

theatre relationshipstheatre 1 to screen 0..*: contains screens. screen.theatre_id → theatre.id.contains screens1 parent · 0..* childrenscreenid [PK]theatre_id [FK]nametheatreid [PK]nametimezone
screen: 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.

screen relationshipsscreen 1 to seat 0..*: contains seats. seat.screen_id → screen.id. screen 1 to show 0..*: hosts shows. show.screen_id → screen.id.contains seats1 parent · 0..* childrenseatid [PK]screen_id [FK]labelhosts shows1 parent · 0..* childrenshowid [PK]movie_id [FK]screen_id [FK]screenid [PK]theatre_id [FK]name
seat: 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.

seat relationshipsseat 1 to show_seat 0..*: is reused across shows. show_seat.seat_id → seat.id.is reused across shows1 parent · 0..* childrenshow_seatid [PK]show_id [FK]seat_id [FK]seatid [PK]screen_id [FK]label
show: 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.

show relationshipsshow 1 to booking_order 0..*: receives bookings. booking_order.show_id → show.id. show 1 to show_seat 0..*: owns inventory. show_seat.show_id → show.id.receives bookings1 parent · 0..* childrenbooking_orderid [PK]user_id [FK]show_id [FK]owns inventory1 parent · 0..* childrenshow_seatid [PK]show_id [FK]seat_id [FK]showid [PK]movie_id [FK]screen_id [FK]
booking_order: 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.

booking_order relationshipsbooking_order 0..1 to show_seat 0..*: currently owns. show_seat.owner_order_id → booking_order.id. booking_order 1 to order_seat 0..*: has price lines. order_seat.order_id → booking_order.id. booking_order 1 to payment_attempt 0..*: has payment evidence. payment_attempt.order_id → booking_order.id.currently owns0..1 parent · 0..* childrenshow_seatid [PK]show_id [FK]seat_id [FK]has price lines1 parent · 0..* childrenorder_seatid [PK]order_id [FK]show_seat_id [FK]has payment evidence1 parent · 0..* childrenpayment_attemptid [PK]order_id [FK]amountbooking_orderid [PK]user_id [FK]show_id [FK]
show_seat: 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.

show_seat relationshipsshow_seat 1 to order_seat 0..*: appears in history. order_seat.show_seat_id → show_seat.id.appears in history1 parent · 0..* childrenorder_seatid [PK]order_id [FK]show_seat_id [FK]show_seatid [PK]show_id [FK]seat_id [FK]

Entity definitions and field contracts

app_user

Purchaser identity.

FieldType / keyMeaning and reference
iduuid · PKAccount identity
emailtext · UKLogin contact

Code mapping: External user adapter.

  • Normalize account identifiers; authorization must bind order owner to authenticated caller.

movie

Movie catalog.

FieldType / keyMeaning and reference
iduuid · PKMovie identity
titletext · VALUEDisplay title
duration_minutesinteger · VALUERuntime

Code mapping: Catalog extension.

  • A movie has many scheduled shows; title is not unique identity.

theatre

Cinema location.

FieldType / keyMeaning and reference
iduuid · PKTheatre identity
nametext · VALUEDisplay label
timezonetext · VALUEIANA display/scheduling zone

Code mapping: Catalog extension.

  • Persist show instants; timezone is for presentation and schedule interpretation.

screen

Auditorium within theatre.

FieldType / keyMeaning and reference
iduuid · PKScreen identity
theatre_iduuid · FKLocation owner · theatre.id
nametext · VALUEAuditorium label

Code mapping: Physical screen catalog extension.

  • Unique(theatre_id,name); prevent overlapping shows via a separate scheduling constraint.

seat

Reusable physical seat.

FieldType / keyMeaning and reference
iduuid · PKPhysical seat identity
screen_iduuid · FKAuditorium owner · screen.id
labeltext · VALUERow/number
categorytext · VALUEPricing category

Code mapping: Seat identifier input; screen metadata extension.

  • Unique(screen_id,label). A seat is not globally sold forever.

show

One screening occurrence.

FieldType / keyMeaning and reference
iduuid · PKShow identity
movie_iduuid · FKScreened movie · movie.id
screen_iduuid · FKAssigned auditorium · screen.id
starts_attimestamp · VALUEAbsolute start instant

Code mapping: Show-scoped booking owner.

  • No conflicting screen time interval; include runtime/cleanup end in real scheduling.

booking_order

One user purchase/hold lifecycle.

FieldType / keyMeaning and reference
iduuid · PKOrder identity
user_iduuid · FKPurchaser · app_user.id
show_iduuid · FKScreening · show.id
stateenum · VALUEHELD CONFIRMED EXPIRED etc
expires_attimestamp · VALUEHold deadline
created_attimestamp · VALUEImmutable acceptance instant for history queries

Code mapping: Hold identity/state; account metadata extension.

  • One order references one show; successful request key binds user/show/exact seat set.

show_seat

Current ownership for one screening seat.

FieldType / keyMeaning and reference
iduuid · PKShow-seat surrogate identity
show_iduuid · FKScreening owner · show.id
seat_iduuid · FKPhysical seat · seat.id
owner_order_iduuid? · FKNullable current hold/sale owner · booking_order.id
versionbigint · VALUEConditional ownership version

Code mapping: Per-show seat ownership map; current relation changes over time.

  • Unique(show_id,seat_id); seat.screen_id must equal show.screen_id, enforced by composite keys/transaction validation. Current owner must belong to same show.

order_seat

Historical order line with immutable price.

FieldType / keyMeaning and reference
iduuid · PKLine identity
order_iduuid · FKPurchase owner · booking_order.id
show_seat_iduuid · FKSelected show-seat · show_seat.id
pricebigint · VALUESnapshotted minor-unit price

Code mapping: Held seat set and trusted price context.

  • Unique(order_id,show_seat_id); history can include expired orders, so do not globally unique show_seat_id here.

payment_attempt

Provider charge evidence and repair status.

FieldType / keyMeaning and reference
idtext · PKStable provider payment identity
order_iduuid · FKCorrelated purchase · booking_order.id
amountbigint · VALUEExpected exact amount
outcomeenum · VALUEUnknown accepted or refund-required
currencytext · VALUECharge currency checked against immutable order prices

Code mapping: Payment outcome ledger; real refund executor is external.

  • Provider identity unique; verify currency/amount/order; distinct extra successful charges require separate refund obligations.

Relationship cardinalities and their reasons

Parent → childMultiplicityFK / why
theatre → screen

contains screens

1 parent per child; 0..* children per parentscreen.theatre_id → theatre.id

A theatre contains many auditoriums.

screen → seat

contains seats

1 parent per child; 0..* children per parentseat.screen_id → screen.id

Physical seats belong to exactly one auditorium.

movie → show

is screened

1 parent per child; 0..* children per parentshow.movie_id → movie.id

A movie has many show occurrences.

screen → show

hosts shows

1 parent per child; 0..* children per parentshow.screen_id → screen.id

The same screen hosts different non-overlapping occurrences.

app_user → booking_order

places orders

1 parent per child; 0..* children per parentbooking_order.user_id → app_user.id

A user may have many orders; ownership authorizes access.

show → booking_order

receives bookings

1 parent per child; 0..* children per parentbooking_order.show_id → show.id

An order buys seats for exactly one show.

show → show_seat

owns inventory

1 parent per child; 0..* children per parentshow_seat.show_id → show.id

Each show materializes its own independent seat availability.

seat → show_seat

is reused across shows

1 parent per child; 0..* children per parentshow_seat.seat_id → seat.id

Physical A12 can be sold once per show, rather than once globally.

booking_order → show_seat

currently owns

0..1 parent per child; 0..* children per parentshow_seat.owner_order_id → booking_order.id

A show-seat has zero or one current order; that order can own many seats. Nullable owner distinguishes available inventory.

booking_order → order_seat

has price lines

1 parent per child; 0..* children per parentorder_seat.order_id → booking_order.id

Order lines preserve selected seats/prices after expiry or completion.

show_seat → order_seat

appears in history

1 parent per child; 0..* children per parentorder_seat.show_seat_id → show_seat.id

Many historical order lines may reference one show-seat; current ownership is constrained elsewhere.

booking_order → payment_attempt

has payment evidence

1 parent per child; 0..* children per parentpayment_attempt.order_id → booking_order.id

At most one charge fulfills the order; extra accepted charges need repair tracking.

Transaction boundaries

  1. Hold: lock requested show_seat rows in sorted identity order, expire eligible prior ownership, validate every seat belongs to the show and is available, then insert order/lines and claim all rows atomically. Never expose half a requested seat group.
  2. Confirm: resolve provider identity, verify owner/show/amount/currency and deadline, then mark order and all still-owned seats confirmed together. A late charge becomes refund-required without stealing seats from a newer order.
  3. Expire: compare current owner order plus HELD state/deadline; release only matching claims. Physical/provider I/O occurs outside this transaction and uses durable outbox/reconciliation in an extension.

Indexes and uniqueness

  1. Unique show_seat(show_id,seat_id), seat(screen_id,label); index show(movie_id,starts_at) and show(screen_id,starts_at).
  2. Index booking_order(user_id,created_at), active booking_order(expires_at), order_seat(order_id), payment_attempt(order_id).
  3. Unique scoped idempotency key with request fingerprint; lock order and current show-seat ownership in one transaction.

Schema traps and failure cases

  1. A foreign key alone cannot enforce seat.screen_id=show.screen_id or owner order.show_id=inventory.show_id; use composite keys or guarded transaction validation.
  2. Deleting expired order history would erase payment/refund evidence. Keep historical lines and clear only conditional current ownership.
  3. Do not implement active uniqueness with a time-dependent now() index predicate; store state/owner explicitly and advance expiry transactionally.

Trace the records through one operation

  1. User U selects show SH1 in screen SC1 and seats A1,A2; SH2 has separate show_seat rows for the same physical seats.
  2. Order O1 atomically holds SH1/A1 and SH1/A2; order_seat stores price snapshots.
  3. O1 expires and O2 claims A1. A late O1 payment records refund-required and cannot change O2 ownership.

Method contracts

hold(show,seats,owner,ttl,key,now) → Hold

Validate identifiers/configuration and checked price/deadline. Sweep expiry, inspect all seats, then claim the whole set and record command identity atomically.

confirm(holdId,owner,paymentId,amount,now) → Confirmation

Resolve matching payment duplicates before new mutation. For a fresh payment validate owner/amount, sweep expiry and confirm only HELD; otherwise record refund obligation.

cancel(holdId,owner,now) → Hold

Enforce owner and current expiry. Release only HELD seats through identity-conditional removal. Reject confirmed cancellation pending a separate policy.

ownerOf(show,seat,now); get(holdId)

ownerOf advances trusted time and sweeps expired records. get returns the latest stored snapshot without inventing a new clock observation.

invariantHolds() → boolean

Diagnostic recount of active hold seats versus ownership map, used in correctness tests rather than needed in every hot-path call.

What, when, why and how

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

Atomic scarce-resource ownership

what
Commit one exclusive claim for every seat in a requested group.
when
Two customers or gates compete for overlapping seat sets.
why
Partial group claims or independent checks can create surprise split bookings and double sales.
how
hold validates all show-seat keys before inserting any ownership. The synchronized boundary also creates hold and dedupe result.

Code: hold, Seat · Invariants: I1

Alternatives, cost and failure example

Alternative: Ordered per-seat locks or a transactional store are alternatives; neither removes the need for one all-or-nothing group contract.

Cost: For K seats, validation/insertion is O(K) expected map work after the expiry scan; the whole service currently serializes.

Counterexample: Bob asks for B,C while Alice holds B; naïvely claiming C first leaves Bob with an unwanted partial group.

Test: Reject the overlapping group and assert C remains free; race identical groups and assert one winner.

Lease deadline enforced by the authoritative owner

what
A hold is exclusive only until a declared instant.
when
Customers abandon checkout or payment arrives late.
why
A timer callback can be late; correctness cannot depend on cleanup executing exactly at the deadline.
how
at(now) sweeps HELD records with expiresAt<=now before relevant ownership decisions. Deadline is immutable; confirmation rechecks after sweeping.

Code: at, expire, confirm · Invariants: I1, I2

Alternatives, cost and failure example

Alternative: A background expiry index improves cleanup cost, but reads/writes still need authoritative deadline enforcement.

Cost: The simple sweep scans H retained holds per clock-advancing call; an expiry heap/index is a measured optimization.

Counterexample: A delayed expiry task allows payment at the exact expired boundary to steal a seat already promised to another owner.

Test: At t=deadline the hold is expired, a new hold may claim the seat, and the old payment becomes REFUND_REQUIRED.

Idempotency and identity-conditional release

what
Resolve repeated logical operations and never release a resource owned by a newer identity.
when
Clients retry or old cleanup events arrive after seat reuse.
why
Deduping only by payload or deleting ownership by seat alone can corrupt the newer booking.
how
Structured Request retains show/set/owner/TTL. Payments retain hold/owner/amount. release uses ownership.remove(key,oldHoldId), and terminal cancel does not release again.

Code: Request, Payment, release, cancel · Invariants: I2, I3

Alternatives, cost and failure example

Alternative: Durable dedupe results must share the transaction with ownership changes; an evictable side cache is insufficient.

Cost: History retention grows; expiry of business records needs an explicit retry/reconciliation horizon.

Counterexample: Old Alice cancellation clears seat A even though it now belongs to Bob.

Test: Expire/reallocate, then retry old cancel and verify Bob remains owner; reject payment-ID reuse for another hold.

Payment outcome versus inventory outcome

what
A successful charge is a fact to reconcile, not permission to rewrite scarce ownership.
when
Money success arrives after cancellation/expiry or twice under distinct payment IDs.
why
Confirming blindly would double-sell; ignoring the charge would lose the customer’s financial repair obligation.
how
confirm records BOOKED only for HELD. All other fresh successful-charge cases yield REFUND_REQUIRED and preserve inventory.

Code: Confirmation, confirm · Invariants: I1, I3

Alternatives, cost and failure example

Alternative: Authorize before final capture where provider semantics support it, but define timeout/expiry and capture races explicitly.

Cost: Refund processing needs its own durable state, retries and audit; this local result does not execute it.

Counterexample: A late charge changes EXPIRED→CONFIRMED even after another customer acquired the same seat.

Test: Confirm an expired hold after reallocation and a second distinct payment on an already confirmed hold; both require refund without ownership change.

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.*;
/** Local multi-show seat inventory. Payment arguments come from a trusted completed-payment adapter. */
public final class Solution {
    private Solution() {}
    public enum State { HELD, CONFIRMED, EXPIRED, CANCELLED }
    public enum Outcome { BOOKED, REFUND_REQUIRED }
    public record Hold(String id,String show,Set<String> seats,String owner,long expiresAt,long amount,State state){public Hold{seats=Set.copyOf(seats);}}
    public record Confirmation(String paymentId,String holdId,Outcome outcome){}
    private record Seat(String show,String seat){}
    private record Request(String show,Set<String> seats,String owner,long ttl){public Request{seats=Set.copyOf(seats);}}
    private record Operation(Request request,String holdId){}
    private record Payment(String holdId,String owner,long amount,Confirmation result){}
    private static void id(String s){if(s==null||s.isBlank())throw new IllegalArgumentException("identifier");}
    public static final class Inventory {
        private final Map<String,Set<String>> shows=new HashMap<>();
        private final long price;
        private final Map<Seat,String> ownership=new HashMap<>();
        private final Map<String,Hold> holds=new LinkedHashMap<>();
        private final Map<String,Operation> operations=new HashMap<>();
        private final Map<String,Payment> payments=new HashMap<>();
        private long sequence,time;
        public Inventory(Map<String,Set<String>> shows,long price){if(price<1)throw new IllegalArgumentException("price");this.price=price;shows.forEach((show,seats)->{id(show);seats.forEach(Solution::id);this.shows.put(show,Set.copyOf(seats));});}
        private void at(long now){if(now<time)throw new IllegalArgumentException("time");time=now;expire();}
        private static Hold state(Hold h,State s){return new Hold(h.id(),h.show(),h.seats(),h.owner(),h.expiresAt(),h.amount(),s);}
        private void release(Hold h){for(String seat:h.seats())ownership.remove(new Seat(h.show(),seat),h.id());}
        private void expire(){for(var h:new ArrayList<>(holds.values()))if(h.state()==State.HELD&&h.expiresAt()<=time){release(h);holds.put(h.id(),state(h,State.EXPIRED));}}
        public synchronized Hold hold(String show,Set<String> seats,String owner,long ttl,String key,long now){
            id(owner);id(key);if(seats==null||seats.isEmpty()||ttl<1)throw new IllegalArgumentException("request");
            var request=new Request(show,seats,owner,ttl);var previous=operations.get(key);
            if(previous!=null){if(!previous.request().equals(request))throw new IllegalArgumentException("key reused");at(now);return get(previous.holdId());}
            var configured=shows.get(show);if(configured==null||!configured.containsAll(seats))throw new IllegalArgumentException("seats");
            long deadline=Math.addExact(now,ttl),amount=Math.multiplyExact(price,seats.size());at(now);
            for(String seat:seats)if(ownership.containsKey(new Seat(show,seat)))throw new IllegalStateException("unavailable");
            var h=new Hold("H"+(++sequence),show,seats,owner,deadline,amount,State.HELD);
            holds.put(h.id(),h);for(String seat:seats)ownership.put(new Seat(show,seat),h.id());operations.put(key,new Operation(request,h.id()));return h;
        }
        public synchronized Confirmation confirm(String holdId,String owner,String paymentId,long amount,long now){
            id(paymentId);var prior=payments.get(paymentId);
            if(prior!=null){if(!prior.holdId().equals(holdId)||!prior.owner().equals(owner)||prior.amount()!=amount)throw new IllegalArgumentException("payment reused");return prior.result();}
            var h=get(holdId);if(!h.owner().equals(owner)||h.amount()!=amount)throw new IllegalArgumentException("owner or amount");at(now);h=get(holdId);
            Outcome outcome;
            if(h.state()==State.HELD){holds.put(h.id(),state(h,State.CONFIRMED));outcome=Outcome.BOOKED;}
            else outcome=Outcome.REFUND_REQUIRED; // Includes a second distinct successful payment after confirmation.
            var result=new Confirmation(paymentId,holdId,outcome);payments.put(paymentId,new Payment(holdId,owner,amount,result));return result;
        }
        public synchronized Hold cancel(String holdId,String owner,long now){var h=get(holdId);if(!h.owner().equals(owner))throw new IllegalArgumentException("owner");at(now);h=get(holdId);if(h.state()==State.CONFIRMED)throw new IllegalStateException("confirmed cancellation needs refund workflow");if(h.state()==State.HELD){release(h);h=state(h,State.CANCELLED);holds.put(h.id(),h);}return h;}
        public synchronized Hold get(String id){var h=holds.get(id);if(h==null)throw new IllegalArgumentException("hold");return h;}
        public synchronized String ownerOf(String show,String seat,long now){at(now);return ownership.get(new Seat(show,seat));}
        public synchronized boolean invariantHolds(){
            int count=0;for(var h:holds.values())if(h.state()==State.HELD||h.state()==State.CONFIRMED){for(String s:h.seats()){count++;if(!h.id().equals(ownership.get(new Seat(h.show(),s))))return false;}}
            return count==ownership.size();
        }
    }
}

hold prepares arithmetic and checks the full group

Compute deadline and total amount with overflow checks, then inspect every seat before any claim. A validation failure cannot create half a group.

expire releases through hold identity

An expired record becomes EXPIRED and its seats are conditionally removed. This protects new owners even if old repair calls repeat.

confirm stores the financial decision

Fresh payment identity and current hold state determine BOOKED versus REFUND_REQUIRED. Matching duplicate payment returns its original outcome instead of re-running ownership logic.

Complexity and limits

  • A fresh hold/confirm/cancel advances time and scans H retained holds for expiry: O(H+K) expected work for K group seats, plus checked fixed-width amount arithmetic.
  • ownerOf also sweeps expiry; it is not a free O(1) read in this implementation. get is expected O(1) without clock advancement. invariantHolds recounts all active seat memberships.
  • Space is O(configured seats + active ownership + retained hold/operation/payment histories). History is unbounded over process lifetime; no persistence or retention cleanup is implemented.
  • A single monitor provides local atomicity but serializes unrelated shows. Per-show partitioning is a plausible next boundary if payment/hold identities and cross-show groups are explicitly scoped.

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.

Group hold and exact expiry

Step 1 of 3: Alice holds A,B until t=10

Both owned by one hold; price 200

Invariant: All-or-nothing group allocation

Read the complete trace table
ActionState after actionInvariant
Alice holds A,B until t=10Both owned by one hold; price 200All-or-nothing group allocation
Bob asks for B,C before expiryRejected; C remains freeNo partial claim
At t=10 Bob holds AAlice expired; Bob owns AExpiry is enforced by authoritative operations

Late payment does not steal a reallocated seat

Step 1 of 2: Alice payment arrives after expiry

REFUND_REQUIRED; Bob still owns A

Invariant: Money success cannot override seat ownership

Read the complete trace table
ActionState after actionInvariant
Alice payment arrives after expiryREFUND_REQUIRED; Bob still owns AMoney success cannot override seat ownership
Retry old cancellationNo release of Bob holdRelease is conditional on original hold identity

Mistakes and counterexamples

Trust a stale seat-map read

Failing case: Two customers both saw A free.

Repair: Recheck and mutate atomically at the ownership boundary.

Make background TTL cleanup authoritative

Failing case: Expiry task runs late.

Repair: Enforce expiresAt during authoritative operations.

Release by seat without checking owner

Failing case: Old hold cleanup runs after reallocation.

Repair: Use stable hold identity and terminal-state guards.

Treat refund-required as refunded

Failing case: The service returned a repair status but provider refund failed.

Repair: Track external compensation separately with its own identity and evidence.

Engineer → Senior → Staff

Choose a deep dive

Implement precise seat/hold behavior.

Executed: all-or-nothing groups, show-seat identity, checked price, expiry and basic confirmation.

Change exercise

Add the same seat label to a second show and demonstrate independence.

Worked answer guidance

Use (show,seat) ownership keys. A raw seat label alone incorrectly links inventory across separate screenings.

Interview follow-ups

Why does get not advance expiry?

It has no trusted now argument. It reports the latest processed state; operations with a time observation perform enforcement.

Why is TTL part of the hold request fingerprint?

Changing TTL changes the requested lease. Same-key retries cannot quietly extend it.

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 · 208/20000

Self-review

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

Open focused practice →

Provenance and scope of evidence

Original teaching material. The local contract is implemented by Solution.java and tested by Test.java on Java 21. Distributed persistence, real provider/device adapters and deployment are not claimed.

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