# Seat Booking With Concurrency — High-Level & Low-Level Design Interview Problems

Source: https://www.skillbyai.com/en/design-interviews/seat-booking

> Holding and confirming seats safely.

## Hold, pay, confirm

In a movie or event booking system (a library system has a similar shape with copies instead of seats), the core risk is **double booking** when two users pick the same seat. Model `Show` -> `Seat` with status AVAILABLE, HELD or BOOKED, and a `Booking` for a user. Flow: the user selects seats, the service places a **temporary hold** with an expiry (for example, an assumed 5-10 minutes), the user pays, and the booking is confirmed. Holds must be atomic across all requested seats: either all are held or none. In a single process, lock per show; in a database, use a conditional update (`UPDATE ... WHERE status = 'AVAILABLE'`) or row locks, and release expired holds with a background sweep or on read.

## Concurrency and data-structure designs

These problems add concurrency and algorithmic design to object modelling.

![Three problems: seat booking with locking, an LRU cache class and a token-bucket rate limiter.](assets/figures/design-interviews/section-4-map.svg) — Figure 4.1 — Seat holds, LRU structure and token bucket.

## All-or-nothing hold

Java sketch with a lock per show.

```java
enum SeatStatus { AVAILABLE, HELD, BOOKED }

class Seat {
    final String id; SeatStatus status = SeatStatus.AVAILABLE;
    String heldBy; java.time.Instant holdExpiry;
    Seat(String id) { this.id = id; }
}

class Show {
    private final java.util.Map<String, Seat> seats = new java.util.HashMap<>();
    private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock();

    boolean hold(java.util.List<String> seatIds, String userId, java.time.Duration ttl) {
        lock.lock();
        try {
            java.time.Instant now = java.time.Instant.now();
            for (String id : seatIds) {
                Seat s = seats.get(id);
                boolean expired = s.status == SeatStatus.HELD && s.holdExpiry.isBefore(now);
                if (s.status != SeatStatus.AVAILABLE && !expired) return false;  // nothing changed yet
            }
            for (String id : seatIds) {
                Seat s = seats.get(id);
                s.status = SeatStatus.HELD; s.heldBy = userId; s.holdExpiry = now.plus(ttl);
            }
            return true;
        } finally {
            lock.unlock();
        }
    }
}

// Database equivalent per seat (all inside one transaction):
// UPDATE seat SET status = 'HELD', held_by = ?, hold_expiry = ?
//  WHERE show_id = ? AND seat_id = ? AND (status = 'AVAILABLE' OR hold_expiry < now())
```

## Check then act inside the lock

Checking availability and updating status must happen in the same critical section or transaction; otherwise two users can both see a seat as free.

**Quiz:** Why use a temporary hold with an expiry?

- [x] So seats are reserved during payment but released if the user abandons
- [ ] To make payment optional
- [ ] To avoid needing a database
- [ ] To let two users share a seat

*Answer:* So seats are reserved during payment but released if the user abandons. Holds protect the payment window without locking seats forever.
