Katkat

Debug and Harden a Ticketing Backend API

IBMSoftware Engineer · Online Assessment

Last updated

Implement and harden an in-memory ticket service backend to handle deterministic cursor pagination, idempotent reservations, cache invalidation, and oversell prevention.

Problem Statement

You are tasked with fixing and hardening the core logic of an event ticketing backend service. The existing implementation suffers from stale cached availability, race conditions that cause overselling, non-deterministic pagination, and lack of idempotency on booking retries.

Implement the TicketBookingService class with the following methods:

  • add_event(event_id: str, name: str, event_date: str, total_seats: int) -> None: Registers a new event. event_date is an ISO-8601 string (YYYY-MM-DD).
  • list_events(limit: int, cursor: str | None = None) -> dict: Returns a paginated list of events ordered chronologically by event_date, breaking ties by event_id in ascending order. The response format is {"events": list[dict], "next_cursor": str | None}. Each event dictionary contains event_id, name, event_date, and available_seats. Use cursor-based pagination so concurrent event additions do not cause duplicate or skipped entries.
  • book_tickets(idempotency_key: str, event_id: str, seat_count: int) -> tuple[int, dict]: Attempts to reserve seat_count tickets. Returns an HTTP-style status code and response payload (status_code, body):
    • 201: Successful booking. Body: {"booking_id": str, "event_id": str, "seat_count": int}.
    • 200: Replay of an identical previous request with the same idempotency_key. Returns the exact body returned when the original booking was made.
    • 400: Invalid inputs (e.g., seat_count <= 0, event does not exist, or idempotency_key reused with mismatched parameters).
    • 409: Conflict due to insufficient available seats. Body: {"error": "Insufficient seats"}.
  • get_available_seats(event_id: str) -> int: Returns the current number of available seats. This must utilize an internal cache for read efficiency, but the cache must never return stale values after a successful booking.

Constraints

  • 1 <= total_seats <= 100,000
  • 1 <= limit <= 100
  • Strings (event_id, idempotency_key, name) consist of printable ASCII characters of length 1 to 64.
  • Up to 10,000 total calls across all methods in a single test run.

Example

service = TicketBookingService()
service.add_event("ev_1", "Concert A", "2026-06-01", 10)
service.add_event("ev_2", "Concert B", "2026-05-01", 5)

# Availability read
assert service.get_available_seats("ev_1") == 10

# Booking tickets
code, res = service.book_tickets("key_1", "ev_1", 4)
# code: 201, res: {"booking_id": "...", "event_id": "ev_1", "seat_count": 4}

# Cached availability is refreshed
assert service.get_available_seats("ev_1") == 6

# Idempotent replay
code_retry, res_retry = service.book_tickets("key_1", "ev_1", 4)
assert code_retry == 200
assert res_retry == res
assert service.get_available_seats("ev_1") == 6

# Oversell attempt
code_err, res_err = service.book_tickets("key_2", "ev_1", 7)
assert code_err == 409

Edge Cases

  • Reusing an idempotency_key with different event_id or seat_count should return 400.
  • Requesting pages with cursor values that point past the end of the collection should return an empty list and None for next_cursor.
  • Booking exactly the remaining number of seats leaves availability at 0 without failing.

Hints

Hint 1

Encode your pagination cursor with the compound key `(event_date, event_id)` so pagination remains stable regardless of newly inserted events.

Hint 2

Store idempotency records mapping each `idempotency_key` to both the request parameters and the resulting response payload.

Hint 3

Ensure cache invalidation or synchronous cache updating happens within the same state transition as the seat reservation to prevent stale read windows.

Follow-up questions

  • How would you handle distributed idempotency and distributed locking across multiple instances backed by Redis and a relational database?
  • If clients need real-time seat selection rather than general admission counts, how does the reservation and lock acquisition pattern change?
  • How would you implement cursor pagination if the user wanted to sort events by dynamic fields such as remaining seat availability?