SYSTEM_DESIGN
System Design: Sports Venue Ticketing
Design a sports venue ticketing system for NFL, NBA, or Premier League scale, covering dynamic pricing, season ticket management, high-concurrency seat selection, and in-venue mobile ticketing with anti-fraud.
Requirements
Functional Requirements:
- Season ticket management: holders get priority access windows before public sale
- Dynamic pricing: ticket prices adjust based on demand, opponent, and days-to-game
- Interactive seat map with real-time availability and price-per-seat display
- Mobile ticket wallet with NFC/QR entry and anti-transfer restrictions
- Group sales: blocks of seats in adjacent sections for corporate buyers
- Resale marketplace with team-approved price floor and ceiling enforcement
Non-Functional Requirements:
- Prevent double-booking with optimistic locking on seat reservations
- Handle 200k concurrent users at public sale start for marquee matchups
- Dynamic pricing recalculates and propagates within 60 seconds of demand signals
- 99.99% availability for entry scanning on game day
- Anti-fraud: detect and block scalper bots at purchase time
Scale Estimation
For an NFL team: 70,000-seat stadium, 10 home games/season. Season ticket holders: 50,000 (71% of capacity). Public sale each game: 20,000 seats. Peak concurrent users at public sale: 200k. Seat availability API reads: 200k users × 5 requests each = 1M requests in first 2 minutes = ~8,333 reads/second. Dynamic pricing model runs every 5 minutes on ~20M price data points per season. Game-day scanning: 70,000 entries in 90 minutes = ~13 scans/second sustained, 400 scans/second at gate rush (gates open 2 hours before kickoff).
High-Level Architecture
The system has four major subsystems: Season Ticket Management, Public Sale Engine, Dynamic Pricing Engine, and Game Day Operations. These share a common Seat Inventory Service and Payment Platform but operate on different time horizons — season tickets are managed months in advance, public sales are event-driven, and game day operations are real-time.
The Dynamic Pricing Engine runs continuously, consuming signals from an Event Data Service (opponent rank, weather, remaining inventory, secondary market prices). It applies a pricing model to compute per-seat prices and publishes updated price maps to a Redis cache. The seat map API always reads from this cache, giving users prices that reflect current demand without hitting the pricing model on every request.
For high-concurrency public sales, the architecture mirrors a flash sale system: a virtual waiting room queues users at sale open, admitting batches in order. Admitted users interact with the seat reservation system using optimistic locking (Redis atomic operations per seat). The group sales flow uses a more complex multi-seat reservation with transactional consistency checks ensuring adjacency constraints are met.
Core Components
Seat Inventory & Reservation Service
Maintains the canonical seat state for every game. Redis Hashes store per-game seat states with atomic Lua scripts for reservation. Season ticket holders' reserved seats are locked as season_hold status at the start of the season and cannot be reserved by other users. The service exposes APIs for single-seat selection, best-available algorithm (finds optimal adjacent seats by section priority), and group block reservation. All state changes publish events to Kafka for downstream consumers (dynamic pricing, analytics, anti-fraud).
Dynamic Pricing Engine
A stream-processing application (Apache Flink or Spark Streaming) that continuously evaluates pricing signals: current inventory sold percentage, time-to-game, secondary market price movements (scraped from StubHub/SeatGeek), opponent win rate, weather (outdoor stadiums). Pricing rules are defined by the team's revenue management team in a configuration layer — not hardcoded. Updated prices are written to Redis and also to a time-series database for pricing analytics. Price change events are published to a Kafka topic; the seat map WebSocket service pushes updates to connected clients.
Anti-Fraud & Bot Detection
Operates at the queue admission layer. Requests are scored by a real-time ML model evaluating: request rate per IP, device fingerprint novelty, behavioral anomalies (instant form fill), purchase velocity (same credit card used across many accounts). Scores above a threshold trigger a CAPTCHA challenge or temporary block. Purchase velocity limits (max 4 tickets per user per sale window) are enforced with Redis rate counters. Detected bot accounts are shadow-banned — they proceed through the flow but their reservations are silently rejected at the final confirmation step.
Database Design
Games: game_id UUID, venue_id, home_team_id, away_team_id, game_date, sale_opens_at. Seats: seat_id, venue_id, section, row, number, tier. Game-seat records: game_seats (game_id, seat_id, status ENUM, current_price, held_by, held_until, season_ticket_holder_id). Current prices are denormalized here for fast seat map generation without joining to the pricing service.
Season tickets: season_tickets (holder_id, team_id, seat_ids[], season_year, auto_renew). Resale listings: resale_listings (listing_id, original_order_id, seat_id, game_id, asking_price, floor_price, ceiling_price, status). Anti-fraud signals: an append-only fraud_signals table consuming Kafka events, used by the model training pipeline. All purchase transactions write to a transactions table with full audit fields.
API Design
GET /api/v1/games/{gameId}/seat-map — returns current seat availability and prices per seat; served from Redis cache (60s TTL).
POST /api/v1/games/{gameId}/reservations — body: {seat_ids[] | strategy: "best_available", count: N, idempotency_key}; atomic reservation with optimistic locking.
POST /api/v1/reservations/{reservationId}/purchase — idempotent payment completion; returns mobile ticket wallet entries.
POST /api/v1/resale/list — season ticket holder lists seats for resale within team-enforced price bounds.
Scaling & Bottlenecks
The single largest scale challenge is the simultaneous release of 20,000 seats to 200k users. The virtual queue system uses Redis Sorted Sets for position management and a token-bucket-based admission rate. The seat reservation Redis cluster is sharded by game_id — all seats for a single game land on the same shard to enable multi-seat atomic operations without cross-shard coordination. For the largest venues, multiple Redis primaries per game using a slot-range partitioning strategy handles the load.
Game-day scanning is a different profile: extremely write-heavy over a short window (2 hours before kickoff), but with strict availability requirements. The scanning service is deployed with extra replicas on game day and uses a local write-through cache (tickets pre-loaded to mobile scanners) so a brief central system outage doesn't halt entry. Scans are durably queued on the scanner device and synced on connectivity restoration.
Key Trade-offs
- Dynamic pricing complexity vs. revenue: Sophisticated demand-based pricing maximizes revenue but risks fan backlash and requires significant ML infrastructure; simple tier-based pricing is easier to implement and explain.
- Season ticket priority window complexity: A pre-sale window for season holders is a business requirement but requires maintaining two separate sale flows with different inventory allocations, adding significant code and operational complexity.
- Anti-fraud strictness vs. user friction: Aggressive bot detection reduces scalping but risks blocking legitimate buyers with shared IPs or automated accessibility tools; a layered approach (soft challenges before hard blocks) balances efficacy and user experience.
- Resale marketplace integration: An official resale marketplace with price floors/ceilings protects fans but reduces secondary market liquidity; strict enforcement requires integrating with legal contracts in season ticket holder agreements.
GO DEEPER
Master this topic in our 12-week cohort
Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.