SwadhyAI Notes

All Low-Level Design notes

Machine coding · not HLD

Movie ticket booking

Two people tap Book on H12 at once. Both UIs flash green. Only one payment may succeed. If your design is "check free, then mark booked," you sold the chair twice. That race is the whole grade.

This page is an in-process seat model: one show, one JVM, one invariant (a seat has at most one owner). A moves FREE → RESERVED → BOOKED via with on an of . Search, payments at city scale, and sold-out storms belong on the related System Design note: Ticketmaster .

Show · row A–B

A1
A2
A3
A4
B1
B2
B3
B4

Green is free, amber reserved, rose booked. The race story lives on H12 (same idea as A3 amber here).

1

tryReserve swaps FREE for RESERVED with owner and TTL as one write.

2

expireDue frees abandoned holds so a crashed browser cannot lock the seat.

3

Same booking id confirms once; a stranger owner fails the CAS.

What actually happens after both UIs flash green

The hero race is the default prompt: two threads call on the same seat. One wins the whole ; the loser must not confirm. with the same booking id returns the same booking, not a second chair. That is a map lookup. See idempotency in code. The race matches parking occupy and inventory reservation; see concurrent state transitions.

Show, Seat, and Booking are the entities. A Show owns seats. Booking is the aggregate you look up later. Seat holds an so status, owner, and move together. Pricing can be a strategy on seat type. The controller is reserve and confirm. Inventory of popcorn is out of scope.

Drawing Kafka and a booking cluster is the wrong interview unless they asked for System Design. A ConcurrentHashMap of seats is the store. Name the related System Design note (see the hero link), then stay on the object model.

How to explain this in an interview

Once both UIs flash green, three questions keep you on the seat model instead of Ticketmaster-scale search.

1. What is the invariant? A seat has at most one owner. Prove it with on , not if (isFree()) book().

2. What frees an abandoned hold? A plus and expireDue. Forever-reserved is a design bug.

3. Did someone say Kafka? City-scale search and sold-out storms belong on the related System Design note (see the hero link). Keep ConcurrentHashMap of seats.

Simple rule of thumb: FREE → RESERVED → BOOKED with owner-bound confirm and release; multi-seat reserve is all-or-rollback (same story as inventory tryReserve of qty 2).

Mechanism: who owns what

With the interview framing set, pin responsibilities before you sketch Java. Each type below owns one job; payment stays outside the Show aggregate.

TypeOwns
ShowOwns show time, screen, and seats. Payment stays outside this aggregate.
Seat of (status, owner, expiresAt). Never leak it to the UI.
BookingHolds seat ids, expiry Instant, and status. Confirm is on booking id.
BookingServiceThin controller: reserve, confirm, and expireDue.
Clock tests freeze , reserve, advance time, then expire holds.

Seat arrows and the Java sketch

The table above is the ownership map. The diagram is the same the code implements; the API sketch is the CAS surface you write in the round.

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
stateDiagram-v2
    [*] --> FREE
    FREE --> RESERVED: tryReserve CAS
    RESERVED --> BOOKED: confirm after pay
    RESERVED --> FREE: TTL expiry
    BOOKED --> [*]
    note right of FREE: two threads: only one CAS wins

Java API sketch

java
enum SeatStatus { FREE, RESERVED, BOOKED }

record SeatState(SeatStatus status, BookingId owner, Instant expiresAt) {}

class Seat {
  private final SeatId id;
  private final AtomicReference<SeatState> state =
      new AtomicReference<>(new SeatState(SeatStatus.FREE, null, null));

  boolean tryReserve(BookingId owner, Instant expiresAt) {
    SeatState free = new SeatState(SeatStatus.FREE, null, null);
    SeatState held = new SeatState(SeatStatus.RESERVED, owner, expiresAt);
    return state.compareAndSet(free, held);
  }

  boolean confirm(BookingId owner) {
    SeatState cur = state.get();
    if (cur.status() != SeatStatus.RESERVED || !Objects.equals(cur.owner(), owner))
      return false;
    SeatState booked = new SeatState(SeatStatus.BOOKED, owner, null);
    return state.compareAndSet(cur, booked);
  }

  boolean release(BookingId owner) {
    SeatState cur = state.get();
    if (cur.status() != SeatStatus.RESERVED || !Objects.equals(cur.owner(), owner))
      return false;
    return state.compareAndSet(cur, new SeatState(SeatStatus.FREE, null, null));
  }
}

class Booking {
  private final BookingId id;
  private final ShowId show;
  private final List<SeatId> seats;
  private final Instant expiresAt;
  private BookingStatus status;
}

class BookingService {
  private final Clock clock;
  Booking reserve(ShowId show, List<SeatId> seats) { /* CAS each seat with owner+TTL */ }
  Booking confirm(BookingId id) { /* payment ok; confirm(owner) per seat; idempotent */ }
  void expireDue(Instant now) { /* RESERVED → FREE when expiresAt passed */ }
}

Worked example: H12, two taps, one chair

Walk the race with the API in hand. Two threads call tryReserve(owner, expires) on H12. One wins the whole . The winner's BookingId is the owner; a different booking cannot confirm or release that hold. Advance a and expireDue / release(owner) returns H12 to FREE. Confirm twice with the same booking id is . Multi-seat: CAS all four or release the ones you already held.

Tests that prove the seams

java
@Test
void onlyOwnerConfirmsAndOnlyOneReserveWins() throws Exception {
  Seat h12 = new Seat("H12");
  BookingId a = new BookingId("A"), b = new BookingId("B");
  Instant exp = T0.plus(Duration.ofMinutes(10));
  AtomicReference<BookingId> winner = new AtomicReference<>();
  Runnable r1 = () -> { if (h12.tryReserve(a, exp)) winner.compareAndSet(null, a); };
  Runnable r2 = () -> { if (h12.tryReserve(b, exp)) winner.compareAndSet(null, b); };
  Thread t1 = new Thread(r1), t2 = new Thread(r2);
  t1.start(); t2.start(); t1.join(); t2.join();
  assertNotNull(winner.get());
  BookingId loser = winner.get().equals(a) ? b : a;
  assertFalse(h12.confirm(loser)); // wrong owner cannot steal confirm
  assertTrue(h12.confirm(winner.get()));
}

Green UI twice

Both threads saw FREE; only one compare-and-set should win the seat.

Forever reserved

If the browser crashes mid-checkout, is what frees the seat.

Invalid cases, concurrency grade, and follow-ups

After the H12 walkthrough, name what must fail and what a follow-up question changes. Concurrency is the grade; pricing and groups are strategies around the same seat machine.

Invalid cases

  • if (seat.isFree()) seat.book() · Two threads both see free; the invariant needs an atomic transition instead.
  • Confirm a FREE seat · Must be RESERVED by this booking's owner token unless you chose a two-state model on purpose.
  • Confirm twice, second booking id · First confirm wins; second owner fails the CAS, not a second chair.
  • TTL never expires · Browser crash holds H12 forever; expireDue is part of the design.
  • One booking across two shows · Booking is per show; split commands rather than sharing one aggregate.

Concurrency is the grade

Multi-seat reserve: all succeed or roll back seats you already reserved. Partial hold strands A3 reserved while A4 failed. Same story as inventory tryReserve of qty 2.

What the follow-up changes

  • Seat types / pricing. Strategy on seat category; the status machine stays unchanged.
  • Group of four together. Allocation picks a block, then CAS each seat with rollback.
  • Scale. Ticketmaster-scale booking is the related System Design note; do not merge Kafka into Seat.java.

Wrap-up

You have the race, the ownership map, and the seam test. Use the forty-five-minute plan below, then follow the related LLD notes for the same concurrent-state pattern.

Forty-five minutes

  1. 0–8Seat.tryReserve(owner, exp) two threads, one win; wrong owner cannot confirm.
  2. 8–20Booking TTL. FakeClock expireDue frees the seat.
  3. 20–32confirm idempotent on booking id.
  4. 32–40Four-seat rollback if one CAS fails.
  5. 40–45Ticketmaster-scale design is the related System Design note: a different interview.

Key takeaway

You're done when two threads racing on the same seat yield one winner via atomic SeatState CAS (not isFree-then-book), booking TTL frees abandoned holds under a FakeClock, confirm is idempotent, and multi-seat reserve is all-or-rollback. City-scale search belongs on the related System Design note.