Core Entities
Event: event_id, name, venue_id, event_date, doors_open, start_time, status (ON_SALE, SOLD_OUT, CANCELLED, COMPLETED), total_capacity. Venue: venue_id, name, address, seating_map_url, total_seats. Section: section_id, venue_id, event_id, name (Floor A, Section 101), capacity, base_price, available_count. Seat: seat_id, section_id, row, seat_number, status (AVAILABLE, HELD, SOLD, BLOCKED), held_by_session_id, held_until. Order: order_id, user_id, event_id, status (PENDING, CONFIRMED, CANCELLED, REFUNDED), total_amount, created_at, expires_at. OrderItem: item_id, order_id, seat_id, price, fee_amount. Ticket: ticket_id, order_item_id, barcode (unique), is_valid, scanned_at.
Seat Hold and Expiry
The core challenge: seats must be reserved while the user completes payment, but not held forever (prevents inventory lock-up). Two-phase approach: Hold (temporary): when user selects seats, lock them for N minutes (typically 10-15 minutes). Payment: user completes payment within the hold window. Confirm: on successful payment, mark seats as SOLD and create tickets.
class SeatService:
HOLD_DURATION_MINUTES = 10
def hold_seats(self, session_id: str, seat_ids: list[int]) -> Order:
with self.db.transaction():
# Lock the rows with SELECT FOR UPDATE
seats = self.repo.lock_seats(seat_ids)
for seat in seats:
if seat.status != SeatStatus.AVAILABLE:
raise SeatUnavailableError(seat.seat_id)
holds_expire_at = datetime.utcnow() + timedelta(minutes=self.HOLD_DURATION_MINUTES)
for seat in seats:
seat.status = SeatStatus.HELD
seat.held_by_session_id = session_id
seat.held_until = holds_expire_at
order = Order(session_id=session_id,
status=OrderStatus.PENDING,
expires_at=holds_expire_at)
self.repo.save_all(seats + [order])
return order
Hold Expiry and Cleanup
A background job runs every 60 seconds: SELECT seat_id FROM seats WHERE status=’HELD’ AND held_until < NOW(). For each expired hold: UPDATE seats SET status='AVAILABLE', held_by_session_id=NULL, held_until=NULL WHERE seat_id=:id AND status='HELD'. Also cancel the associated pending order. Optimistic check (AND status='HELD'): prevents releasing a seat that was just confirmed by a concurrent payment. After releasing: increment section.available_count so the section map shows updated availability. Redis pub/sub: broadcast the seat availability change so open browser sessions can update their seat maps in real time without polling.
Payment and Ticket Issuance
class CheckoutService:
def confirm_order(self, order_id: int, payment_token: str) -> list[Ticket]:
with self.db.transaction():
order = self.repo.lock_order(order_id) # SELECT FOR UPDATE
if order.status != OrderStatus.PENDING:
raise OrderNotPendingError()
if datetime.utcnow() > order.expires_at:
self._cancel_order(order)
raise OrderExpiredError()
# Process payment
charge = self.payment.charge(payment_token, order.total_amount,
idempotency_key=str(order_id))
if charge.status != "succeeded":
raise PaymentFailedError(charge.decline_reason)
# Confirm seats
seats = self.repo.get_seats_for_order(order_id)
for seat in seats:
seat.status = SeatStatus.SOLD
order.status = OrderStatus.CONFIRMED
order.payment_id = charge.id
# Generate tickets with unique barcodes
tickets = [Ticket(seat_id=s.seat_id,
barcode=self._generate_barcode(),
is_valid=True)
for s in seats]
self.repo.save_all(seats + [order] + tickets)
return tickets
High-Concurrency Seat Selection
For popular events (Taylor Swift, playoff tickets): thousands of users simultaneously try to select the same seats. The SELECT FOR UPDATE approach works but creates lock contention. Optimizations: (1) Virtual queue: admit users to the seat selection flow in batches (like flash sale waiting room). Reduces simultaneous seat selection attempts. (2) Section-level inventory: show sections with available_count > 0 on the event map. Only allow entering seat-level selection for sections with availability. Reduces wasted attempts. (3) Auto-assign for General Admission: instead of user-selected seats, system assigns seats automatically (best available algorithm). Eliminates seat selection contention entirely. (4) Read replica for browsing: serve the event map and seat availability from a read replica. Only route to the primary when actually holding a seat.
Asked at: Stripe Interview Guide
Asked at: Shopify Interview Guide
Asked at: Airbnb Interview Guide
Asked at: Uber Interview Guide