A backend service for managing event tickets with thread-safe booking operations. Built with Python and FastAPI.
This system allows users to create events, check seat availability, book seats, and cancel bookings. All data is stored in-memory with proper concurrency handling to prevent race conditions during simultaneous booking attempts.
Ticketing_system/
βββ models.py β Data structures (Events and Bookings)
βββ storage.py β In-memory storage with thread safety
βββ business_logic.py β Core booking logic (race condition prevention)
βββ main.py β FastAPI routes (API endpoints)
βββ test_api.py β Tests including concurrency test
βββ requirements.txt β Dependencies
βββ README.md β This file
cd Ticketing_system
pip install -r requirements.txtuvicorn main:app --reloadYou should see:
==================================================
TICKETING SYSTEM API STARTED
==================================================
API Documentation: http://localhost:8000/docs
Test it: http://localhost:8000/
==================================================
Go to: http://localhost:8000/docs
You'll see an interactive API page where you can test everything!
Defines what our data looks like using Pydantic models:
- Event: name, date, venue, total_seats, available_seats
- Booking: event_id, num_seats, status (confirmed/cancelled)
Key Point: Pydantic automatically validates data for us!
Stores data in Python dictionaries:
events = {event_id: Event object}
bookings = {booking_id: Booking object}Key Point: Uses threading.Lock() to prevent race conditions!
Contains the actual logic for:
- Creating events
- Making bookings (with race condition prevention!)
- Cancelling bookings
Most Important Part: The create_booking() function uses a lock:
with booking_lock: # Only ONE person can book at a time
# Check if seats available
# Reduce available seats
# Create booking
# Lock released - next person can bookThis prevents overbooking when multiple people book simultaneously!
Defines all the URLs you can call:
Events:
POST /events- Create eventGET /events- List all eventsGET /events/{id}- Get event detailsGET /events/{id}/availability- Check seats
Bookings:
POST /bookings- Book seatsGET /bookings/{id}- Get booking detailsPATCH /bookings/{id}/cancel- Cancel booking
Tests including the critical race condition test:
- 5 people try to book 3 seats each from event with 10 total seats
- Only 3 succeed (9 seats), 2 fail
- Proves our locking works!
pytest test_api.py -vExpected output:
test_create_event PASSED
test_list_events PASSED
test_check_availability PASSED
test_create_booking PASSED
test_booking_not_enough_seats PASSED
test_cancel_booking PASSED
test_concurrent_bookings_no_overbooking PASSED
RACE CONDITION TEST PASSED!
- 3 bookings succeeded (correct)
- 2 bookings failed (correct)
- 1 seat remaining (correct)
The application follows a clean, layered architecture pattern:
βββββββββββββββββββββββββββββββββββββββ
β API Layer (FastAPI - main.py) β β HTTP endpoints, request/response handling
βββββββββββββββββββββββββββββββββββββββ€
β Business Logic (business_logic.py) β β Core booking logic, race condition prevention
βββββββββββββββββββββββββββββββββββββββ€
β Storage Layer (storage.py) β β In-memory data storage with thread safety
βββββββββββββββββββββββββββββββββββββββ€
β Data Models (models.py) β β Pydantic models for validation
βββββββββββββββββββββββββββββββββββββββ
1. API Layer (main.py)
- Defines HTTP endpoints (POST /events, POST /bookings, etc.)
- Handles request validation using Pydantic
- Returns standardized JSON responses
- Delegates business logic to the service layer
2. Business Logic Layer (business_logic.py)
- Implements core operations (create event, create booking, cancel booking)
- Enforces business rules (seat availability validation)
- Contains the critical race condition prevention mechanism
- Orchestrates calls to the storage layer
3. Storage Layer (storage.py)
- Manages in-memory data structures (dictionaries for O(1) lookups)
- Provides thread-safe data access using
threading.Lock() - Handles CRUD operations for events and bookings
4. Data Models (models.py)
- Defines structure for Events and Bookings using Pydantic
- Automatic validation (dates must be in future, seats must be positive)
- Type safety throughout the application
1. Client β POST /bookings {"event_id": "...", "num_seats": 2}
2. API Layer validates request using Pydantic models
3. API calls business_logic.create_booking()
4. Business Logic:
a. Acquires lock (prevents race conditions)
b. Checks event exists
c. Validates seat availability
d. Updates available seats (via storage layer)
e. Creates booking record
f. Releases lock
5. API returns booking confirmation to client
Problem: When multiple users try to book seats simultaneously, there's a risk of overbooking. Without proper synchronization:
- User A checks: 5 seats available β
- User B checks: 5 seats available β
- User A books 3 seats β 2 remaining
- User B books 3 seats β OVERBOOKING! (-1 seats)
Solution:
I used Python's threading.Lock() to make the entire check-and-update operation atomic.
# In business_logic.py
booking_lock = threading.Lock()
def create_booking(booking_data):
with booking_lock: # Only ONE booking at a time
# 1. Check event exists
# 2. Check seat availability
# 3. Reduce available seats
# 4. Create booking
# All steps happen atomically
# Lock released - next booking can proceedWhy this works:
- The
with booking_lock:ensures only one thread can execute the booking logic at a time - Other threads wait their turn
- By the time the second user's request is processed, they see the updated seat count
- No overbooking possible
Verification:
The test_concurrent_bookings_no_overbooking test proves this works by simulating 5 simultaneous booking attempts on a 10-seat event. Only 3 succeed (using 9 seats), 2 fail correctly.
Decision: Use Python dictionaries with UUID keys for data storage.
events = {event_id: Event_object}
bookings = {booking_id: Booking_object}Decision: Use separate threading.Lock() instances for event and booking operations.
Why:
- Better concurrency: Reading event details doesn't block booking operations
- Prevents unnecessary waiting
- More granular control over critical sections
Could improve: Use per-event locks instead of global lock for even better concurrency (explained in "Future Improvements" section).
Issue: All data is stored in memory and lost when the server restarts.
Impact: Not suitable for production use. Every server restart erases all events and bookings.
Issue: The threading.Lock() only works within a single Python process.
Impact: Cannot scale horizontally by adding more server instances. If you run multiple servers behind a load balancer, each has its own memory and locks don't synchronize across processes.
Issue: Only one booking can be processed at a time, even for different events.
Impact: Potential bottleneck under high load. If 1000 people are booking tickets for different events, they all wait in line.
Better approach: Per-event locks (explained in improvements section).
Current: In-memory dictionaries Improvement: PostgreSQL database
Current: threading.Lock() (single process only)
Improvement: Redis-based distributed locks
Current: Global lock for all bookings Improvement: Separate lock for each event