Build a Concurrency-Safe Booking Engine

Prevent double-bookings using DynamoDB conditional writes and optimistic locking.

Introduction

30 Second Summary

Think about grabbing the last seat at a sold-out concert. Thousands of people click "Buy" at the exact same moment. How does the system guarantee only one person gets that seat?

In this project, you will build a Python booking engine called CloudTicket that prevents double-booking even when 50 users try to reserve the same seat simultaneously. You will use DynamoDB conditional writes, a state machine, and idempotency keys to guarantee correctness under heavy concurrent load.

What You'll Build

A concurrency-safe seat reservation system powered by DynamoDB conditional writes, a booking lifecycle state machine, and a test harness that proves zero double-bookings under load.

By the end of this project, you'll have:

  • A DynamoDB table designed for a booking system with partition/sort key structure and optimistic locking via conditional writes.
  • A booking engine implementing atomic seat reservation, idempotency keys to prevent duplicate operations, and TTL-based reservation expiry.
  • A concurrency test harness that simulates 50 simultaneous users competing for the same seat and proves exactly one winner.
  • Secret Mission: implement a retry-with-backoff strategy and a waiting list that automatically promotes the next user when a held seat expires.

Are there any prerequisites?

You need an AWS account (free tier eligible) and basic Python experience. The project uses the DynamoDB free tier, so there is no cost as long as you stay within the 25 WCU / 25 RCU / 25 GB provisioned limits.

Build the Booking State Machine and Atomic Seat Reservation

Your tables are seeded and ready. Now you need to answer the core question: how do you prevent two users from booking the same seat at the exact same time?

The answer is a combination of a state machine (which defines the rules for how a booking can move between states) and conditional writes (which enforce those rules atomically in DynamoDB). In this step, you will build both and prove they work together.

In this step, get ready to:
  • Define a booking state machine with valid lifecycle transitions.
  • Implement atomic seat reservation using DynamoDB conditional writes.
  • Manually test the hold, confirm, and cancel flow against live data.
Design and implement the booking state machine

A booking goes through a lifecycle: it starts as AVAILABLE, gets HELD by a user, moves to CONFIRMED after payment, and can be CANCELLED at any point. Without a formal set of rules governing these transitions, your code could accidentally move a seat from CONFIRMED straight back to HELD.

  • Open state_machine.py in VS Code (it should be empty).
  • Add the full state machine code by pasting the following:
class InvalidTransitionError(Exception):
    """Raised when an invalid state transition is attempted."""

    def __init__(self, current_state, target_state):
        self.current_state = current_state
        self.target_state = target_state
        super().__init__(
            f"Invalid transition: {current_state} -> {target_state}"
        )


class BookingStateMachine:
    """Governs valid booking lifecycle transitions."""

    VALID_TRANSITIONS = {
        "AVAILABLE": ["HELD"],
        "HELD": ["CONFIRMED", "CANCELLED", "EXPIRED", "AVAILABLE"],
        "CONFIRMED": ["CANCELLED"],
        "CANCELLED": [],
        "EXPIRED": [],
    }

    @classmethod
    def validate_transition(cls, current_state, target_state):
        """Validate that a state transition is allowed."""
        if current_state not in cls.VALID_TRANSITIONS:
            raise InvalidTransitionError(current_state, target_state)

        if target_state not in cls.VALID_TRANSITIONS[current_state]:
            raise InvalidTransitionError(current_state, target_state)

        return True

    @classmethod
    def get_valid_targets(cls, current_state):
        """Return valid target states from the current state."""
        return cls.VALID_TRANSITIONS.get(current_state, [])

What does this code do?

VALID_TRANSITIONS is a dictionary that maps every booking state to the list of states it can move to. For example, AVAILABLE can only become HELD. CANCELLED and EXPIRED are terminal states with no outgoing transitions.

The validate_transition() classmethod checks whether a proposed state change is legal. If not, it raises InvalidTransitionError with a clear message showing what went wrong.

  • Save state_machine.py.

Why a state machine instead of simple if-else checks?

A state machine centralises all the transition rules in one place. If you later add a new state (like REFUNDED), you update one dictionary instead of hunting through scattered conditionals across your codebase.

It also makes invalid transitions fail loudly with a named exception, which is much easier to debug than a silent wrong-state bug.

Implement atomic seat reservation with conditional writes

The state machine defines the rules. Now you need to enforce them atomically in the database. Optimistic locking is the pattern: attempt the write with a condition, and if the condition fails, someone else got there first.

The booking engine also needs an idempotency layer (which you will build fully in the next step). For now, you will create a temporary placeholder so the imports work.

  • Open idempotency.py and add this temporary stub:
class IdempotencyStore:
    """Temporary placeholder - full implementation comes next step."""

    def __init__(self, **kwargs):
        pass

    def check_and_claim(self, idempotency_key):
        # Always proceed with the operation for now
        return True, None

    def store_result(self, idempotency_key, result):
        # No-op until real implementation
        pass

Why a placeholder?

The booking engine imports IdempotencyStore to prevent duplicate operations. You will replace this stub with the full conditional-write implementation in Step 3. For now, it simply allows every operation to proceed.

  • Save idempotency.py.
  • Open booking_engine.py (it should be empty).
  • Start by adding the imports and the BookingResult class:
import time
import uuid
import boto3
from botocore.exceptions import ClientError
from state_machine import BookingStateMachine, InvalidTransitionError
from idempotency import IdempotencyStore


class BookingResult:
    """Represents the outcome of a booking operation."""

    def __init__(self, success, message, data=None):
        self.success = success
        self.message = message
        self.data = data or {}

    def to_dict(self):
        return {
            "success": self.success,
            "message": self.message,
            "data": self.data,
        }

What does this code do?

BookingResult is a simple container that every booking operation returns. It carries a success boolean, a human-readable message, and an optional data dictionary with details like the seat ID or expiry time.

The to_dict() method serialises the result so it can be stored for idempotency checks later.

  • Below the BookingResult class, add the BookingEngine class with its __init__ and the hold_seat() method:
class BookingEngine:
    """Concurrency-safe booking engine using DynamoDB conditional writes."""

    HOLD_DURATION_SECONDS = 300  # 5 minutes

    def __init__(self, table_name="CloudTicket-Bookings", region="us-east-1"):
        self.table = boto3.resource("dynamodb", region_name=region).Table(table_name)
        self.idempotency = IdempotencyStore(region=region)

    def hold_seat(self, event_id, seat_id, user_id, idempotency_key=None):
        """
        Attempt to hold a seat for a user.
        Uses conditional writes to prevent double-booking.
        """
        if idempotency_key is None:
            idempotency_key = f"hold-{event_id}-{seat_id}-{user_id}-{uuid.uuid4()}"

        # Check idempotency
        is_new, stored_result = self.idempotency.check_and_claim(idempotency_key)
        if not is_new:
            if stored_result:
                return BookingResult(**stored_result)
            return BookingResult(
                success=False, message="Duplicate request in progress"
            )

        # Validate state transition
        BookingStateMachine.validate_transition("AVAILABLE", "HELD")

        now = int(time.time())
        expires_at = now + self.HOLD_DURATION_SECONDS

        try:
            self.table.update_item(
                Key={"event_id": event_id, "seat_id": seat_id},
                UpdateExpression=(
                    "SET booking_status = :new_status, "
                    "held_by = :user_id, "
                    "held_at = :now, "
                    "expires_at = :expires_at, "
                    "version = version + :inc"
                ),
                ConditionExpression=(
                    "(booking_status = :available AND attribute_exists(event_id)) OR "
                    "(booking_status = :held AND expires_at < :current_time)"
                ),
                ExpressionAttributeValues={
                    ":new_status": "HELD",
                    ":user_id": user_id,
                    ":now": now,
                    ":expires_at": expires_at,
                    ":inc": 1,
                    ":available": "AVAILABLE",
                    ":held": "HELD",
                    ":current_time": now,
                },
                ReturnValues="ALL_NEW",
            )

            result = BookingResult(
                success=True,
                message=f"Seat {seat_id} held for user {user_id}",
                data={
                    "event_id": event_id,
                    "seat_id": seat_id,
                    "user_id": user_id,
                    "expires_at": expires_at,
                    "status": "HELD",
                },
            )
        except ClientError as e:
            if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                result = BookingResult(
                    success=False,
                    message=f"Seat {seat_id} is unavailable (already held or booked)",
                    data={"event_id": event_id, "seat_id": seat_id},
                )
            else:
                raise

        # Store idempotency result
        self.idempotency.store_result(idempotency_key, result.to_dict())
        return result

What does hold_seat do?

This is the core of your concurrency protection. The ConditionExpression tells DynamoDB: only update this seat if it is currently AVAILABLE, OR if it is HELD but the hold has expired. If another user already holds the seat, the condition evaluates to false and DynamoDB rejects the write with a ConditionalCheckFailedException.

The version = version + :inc part increments a version counter on every successful write. This counter enables optimistic locking in the confirm_booking() method you will add next.

  • Below hold_seat(), add the confirm_booking() method inside the BookingEngine class:
    def confirm_booking(self, event_id, seat_id, user_id, expected_version):
        """
        Confirm a held seat. Uses optimistic locking via version check.
        """
        BookingStateMachine.validate_transition("HELD", "CONFIRMED")

        now = int(time.time())

        try:
            self.table.update_item(
                Key={"event_id": event_id, "seat_id": seat_id},
                UpdateExpression=(
                    "SET booking_status = :new_status, "
                    "confirmed_at = :now, "
                    "version = version + :inc, "
                    "expires_at = :no_expiry"
                ),
                ConditionExpression=(
                    "booking_status = :held AND "
                    "held_by = :user_id AND "
                    "version = :expected_version"
                ),
                ExpressionAttributeValues={
                    ":new_status": "CONFIRMED",
                    ":now": now,
                    ":inc": 1,
                    ":no_expiry": None,
                    ":held": "HELD",
                    ":user_id": user_id,
                    ":expected_version": expected_version,
                },
                ReturnValues="ALL_NEW",
            )

            return BookingResult(
                success=True,
                message=f"Seat {seat_id} confirmed for user {user_id}",
                data={
                    "event_id": event_id,
                    "seat_id": seat_id,
                    "user_id": user_id,
                    "status": "CONFIRMED",
                },
            )
        except ClientError as e:
            if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                return BookingResult(
                    success=False,
                    message="Confirmation failed: seat state changed or version mismatch",
                    data={"event_id": event_id, "seat_id": seat_id},
                )
            raise

How does optimistic locking work here?

The confirm_booking() method checks three conditions simultaneously: the seat must still be HELD, the user making the request must be the one holding it, and the version must match what the caller expects.

If any process modifies the item between the read and the confirm attempt, the version increments and this condition fails. That is optimistic locking: assume no conflict, detect it at write time.

  • Below confirm_booking(), add the cancel_booking() and release_expired_holds() methods:
    def cancel_booking(self, event_id, seat_id, user_id):
        """Cancel a held or confirmed booking, returning seat to AVAILABLE."""
        try:
            self.table.update_item(
                Key={"event_id": event_id, "seat_id": seat_id},
                UpdateExpression=(
                    "SET booking_status = :new_status, "
                    "held_by = :none, "
                    "held_at = :none, "
                    "confirmed_at = :none, "
                    "expires_at = :none, "
                    "version = version + :inc"
                ),
                ConditionExpression=(
                    "(booking_status = :held OR booking_status = :confirmed) AND "
                    "held_by = :user_id"
                ),
                ExpressionAttributeValues={
                    ":new_status": "AVAILABLE",
                    ":none": None,
                    ":inc": 1,
                    ":held": "HELD",
                    ":confirmed": "CONFIRMED",
                    ":user_id": user_id,
                },
            )

            return BookingResult(
                success=True,
                message=f"Booking cancelled for seat {seat_id}",
                data={"event_id": event_id, "seat_id": seat_id, "status": "AVAILABLE"},
            )
        except ClientError as e:
            if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                return BookingResult(
                    success=False,
                    message="Cancellation failed: not your booking or invalid state",
                )
            raise

    def release_expired_holds(self, event_id):
        """Scan for expired holds and release them back to AVAILABLE."""
        now = int(time.time())
        released = []

        response = self.table.query(
            KeyConditionExpression=boto3.dynamodb.conditions.Key("event_id").eq(
                event_id
            ),
            FilterExpression=(
                boto3.dynamodb.conditions.Attr("booking_status").eq("HELD")
                & boto3.dynamodb.conditions.Attr("expires_at").lt(now)
            ),
        )

        for item in response.get("Items", []):
            try:
                self.table.update_item(
                    Key={
                        "event_id": item["event_id"],
                        "seat_id": item["seat_id"],
                    },
                    UpdateExpression=(
                        "SET booking_status = :new_status, "
                        "held_by = :none, "
                        "held_at = :none, "
                        "expires_at = :none, "
                        "version = version + :inc"
                    ),
                    ConditionExpression=(
                        "booking_status = :held AND expires_at < :now"
                    ),
                    ExpressionAttributeValues={
                        ":new_status": "AVAILABLE",
                        ":none": None,
                        ":inc": 1,
                        ":held": "HELD",
                        ":now": now,
                    },
                )
                released.append(item["seat_id"])
            except ClientError as e:
                if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                    continue
                raise

        return released

What do these methods do?

cancel_booking() resets a seat back to AVAILABLE. Its condition ensures only the user who holds the booking can cancel it, and the seat must currently be in HELD or CONFIRMED state.

release_expired_holds() is a cleanup method. It queries for all HELD seats whose expires_at timestamp has passed, then conditionally transitions each one back to AVAILABLE. The condition check on each release prevents race conditions where a seat gets confirmed between the query and the update.

  • Save booking_engine.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved both state_machine.py, idempotency.py, and booking_engine.py.

ⓧ I'd like to double check the full code

Here is the complete state_machine.py:

class InvalidTransitionError(Exception):
    """Raised when an invalid state transition is attempted."""

    def __init__(self, current_state, target_state):
        self.current_state = current_state
        self.target_state = target_state
        super().__init__(
            f"Invalid transition: {current_state} -> {target_state}"
        )


class BookingStateMachine:
    """Governs valid booking lifecycle transitions."""

    VALID_TRANSITIONS = {
        "AVAILABLE": ["HELD"],
        "HELD": ["CONFIRMED", "CANCELLED", "EXPIRED", "AVAILABLE"],
        "CONFIRMED": ["CANCELLED"],
        "CANCELLED": [],
        "EXPIRED": [],
    }

    @classmethod
    def validate_transition(cls, current_state, target_state):
        """Validate that a state transition is allowed."""
        if current_state not in cls.VALID_TRANSITIONS:
            raise InvalidTransitionError(current_state, target_state)

        if target_state not in cls.VALID_TRANSITIONS[current_state]:
            raise InvalidTransitionError(current_state, target_state)

        return True

    @classmethod
    def get_valid_targets(cls, current_state):
        """Return valid target states from the current state."""
        return cls.VALID_TRANSITIONS.get(current_state, [])

Here is the complete booking_engine.py:

import time
import uuid
import boto3
from botocore.exceptions import ClientError
from state_machine import BookingStateMachine, InvalidTransitionError
from idempotency import IdempotencyStore


class BookingResult:
    """Represents the outcome of a booking operation."""

    def __init__(self, success, message, data=None):
        self.success = success
        self.message = message
        self.data = data or {}

    def to_dict(self):
        return {
            "success": self.success,
            "message": self.message,
            "data": self.data,
        }


class BookingEngine:
    """Concurrency-safe booking engine using DynamoDB conditional writes."""

    HOLD_DURATION_SECONDS = 300  # 5 minutes

    def __init__(self, table_name="CloudTicket-Bookings", region="us-east-1"):
        self.table = boto3.resource("dynamodb", region_name=region).Table(table_name)
        self.idempotency = IdempotencyStore(region=region)

    def hold_seat(self, event_id, seat_id, user_id, idempotency_key=None):
        """
        Attempt to hold a seat for a user.
        Uses conditional writes to prevent double-booking.
        """
        if idempotency_key is None:
            idempotency_key = f"hold-{event_id}-{seat_id}-{user_id}-{uuid.uuid4()}"

        # Check idempotency
        is_new, stored_result = self.idempotency.check_and_claim(idempotency_key)
        if not is_new:
            if stored_result:
                return BookingResult(**stored_result)
            return BookingResult(
                success=False, message="Duplicate request in progress"
            )

        # Validate state transition
        BookingStateMachine.validate_transition("AVAILABLE", "HELD")

        now = int(time.time())
        expires_at = now + self.HOLD_DURATION_SECONDS

        try:
            self.table.update_item(
                Key={"event_id": event_id, "seat_id": seat_id},
                UpdateExpression=(
                    "SET booking_status = :new_status, "
                    "held_by = :user_id, "
                    "held_at = :now, "
                    "expires_at = :expires_at, "
                    "version = version + :inc"
                ),
                ConditionExpression=(
                    "(booking_status = :available AND attribute_exists(event_id)) OR "
                    "(booking_status = :held AND expires_at < :current_time)"
                ),
                ExpressionAttributeValues={
                    ":new_status": "HELD",
                    ":user_id": user_id,
                    ":now": now,
                    ":expires_at": expires_at,
                    ":inc": 1,
                    ":available": "AVAILABLE",
                    ":held": "HELD",
                    ":current_time": now,
                },
                ReturnValues="ALL_NEW",
            )

            result = BookingResult(
                success=True,
                message=f"Seat {seat_id} held for user {user_id}",
                data={
                    "event_id": event_id,
                    "seat_id": seat_id,
                    "user_id": user_id,
                    "expires_at": expires_at,
                    "status": "HELD",
                },
            )
        except ClientError as e:
            if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                result = BookingResult(
                    success=False,
                    message=f"Seat {seat_id} is unavailable (already held or booked)",
                    data={"event_id": event_id, "seat_id": seat_id},
                )
            else:
                raise

        # Store idempotency result
        self.idempotency.store_result(idempotency_key, result.to_dict())
        return result

    def confirm_booking(self, event_id, seat_id, user_id, expected_version):
        """
        Confirm a held seat. Uses optimistic locking via version check.
        """
        BookingStateMachine.validate_transition("HELD", "CONFIRMED")

        now = int(time.time())

        try:
            self.table.update_item(
                Key={"event_id": event_id, "seat_id": seat_id},
                UpdateExpression=(
                    "SET booking_status = :new_status, "
                    "confirmed_at = :now, "
                    "version = version + :inc, "
                    "expires_at = :no_expiry"
                ),
                ConditionExpression=(
                    "booking_status = :held AND "
                    "held_by = :user_id AND "
                    "version = :expected_version"
                ),
                ExpressionAttributeValues={
                    ":new_status": "CONFIRMED",
                    ":now": now,
                    ":inc": 1,
                    ":no_expiry": None,
                    ":held": "HELD",
                    ":user_id": user_id,
                    ":expected_version": expected_version,
                },
                ReturnValues="ALL_NEW",
            )

            return BookingResult(
                success=True,
                message=f"Seat {seat_id} confirmed for user {user_id}",
                data={
                    "event_id": event_id,
                    "seat_id": seat_id,
                    "user_id": user_id,
                    "status": "CONFIRMED",
                },
            )
        except ClientError as e:
            if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                return BookingResult(
                    success=False,
                    message="Confirmation failed: seat state changed or version mismatch",
                    data={"event_id": event_id, "seat_id": seat_id},
                )
            raise

    def cancel_booking(self, event_id, seat_id, user_id):
        """Cancel a held or confirmed booking, returning seat to AVAILABLE."""
        try:
            self.table.update_item(
                Key={"event_id": event_id, "seat_id": seat_id},
                UpdateExpression=(
                    "SET booking_status = :new_status, "
                    "held_by = :none, "
                    "held_at = :none, "
                    "confirmed_at = :none, "
                    "expires_at = :none, "
                    "version = version + :inc"
                ),
                ConditionExpression=(
                    "(booking_status = :held OR booking_status = :confirmed) AND "
                    "held_by = :user_id"
                ),
                ExpressionAttributeValues={
                    ":new_status": "AVAILABLE",
                    ":none": None,
                    ":inc": 1,
                    ":held": "HELD",
                    ":confirmed": "CONFIRMED",
                    ":user_id": user_id,
                },
            )

            return BookingResult(
                success=True,
                message=f"Booking cancelled for seat {seat_id}",
                data={"event_id": event_id, "seat_id": seat_id, "status": "AVAILABLE"},
            )
        except ClientError as e:
            if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                return BookingResult(
                    success=False,
                    message="Cancellation failed: not your booking or invalid state",
                )
            raise

    def release_expired_holds(self, event_id):
        """Scan for expired holds and release them back to AVAILABLE."""
        now = int(time.time())
        released = []

        response = self.table.query(
            KeyConditionExpression=boto3.dynamodb.conditions.Key("event_id").eq(
                event_id
            ),
            FilterExpression=(
                boto3.dynamodb.conditions.Attr("booking_status").eq("HELD")
                & boto3.dynamodb.conditions.Attr("expires_at").lt(now)
            ),
        )

        for item in response.get("Items", []):
            try:
                self.table.update_item(
                    Key={
                        "event_id": item["event_id"],
                        "seat_id": item["seat_id"],
                    },
                    UpdateExpression=(
                        "SET booking_status = :new_status, "
                        "held_by = :none, "
                        "held_at = :none, "
                        "expires_at = :none, "
                        "version = version + :inc"
                    ),
                    ConditionExpression=(
                        "booking_status = :held AND expires_at < :now"
                    ),
                    ExpressionAttributeValues={
                        ":new_status": "AVAILABLE",
                        ":none": None,
                        ":inc": 1,
                        ":held": "HELD",
                        ":now": now,
                    },
                )
                released.append(item["seat_id"])
            except ClientError as e:
                if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                    continue
                raise

        return released
Test the booking flow manually

You have the state machine and the engine. Time to prove they work together against real data in your DynamoDB table.

  • Open a terminal in your cloudticket/ directory.
  • Start a Python REPL by running:
python3 -i -c "from booking_engine import BookingEngine; engine = BookingEngine(); print('Engine ready')"

What does this command do?

The -i flag keeps the Python interpreter open after executing the setup code. The -c flag runs the quoted code inline. After this, you have an engine object ready to use in the REPL.

  • Hold a seat by running:
result = engine.hold_seat("EVT-001", "SEAT-001", "user-alice")
print(result.success, result.message)
  • You should see True Seat SEAT-001 held for user user-alice printed.
  • Now try to hold the same seat with a different user:
result2 = engine.hold_seat("EVT-001", "SEAT-001", "user-bob")
print(result2.success, result2.message)
  • You should see False Seat SEAT-001 is unavailable (already held or booked) printed. No crash, no exception. The conditional write handled the conflict gracefully.
  • Confirm Alice's booking (the version is now 1 after the hold):
confirm = engine.confirm_booking("EVT-001", "SEAT-001", "user-alice", 1)
print(confirm.success, confirm.message)
  • You should see True Seat SEAT-001 confirmed for user user-alice.
  • Cancel the confirmed booking:
cancel = engine.cancel_booking("EVT-001", "SEAT-001", "user-alice")
print(cancel.success, cancel.message)
  • You should see True Booking cancelled for seat SEAT-001. The seat is now back to AVAILABLE.
  • Exit the REPL by typing exit().

Seeing an ImportError or ModuleNotFoundError?

Make sure your terminal is in the cloudticket/ directory (where booking_engine.py, state_machine.py, and idempotency.py all live). Python needs to find these files in the current working directory.

If you see No module named 'botocore', verify that boto3 is installed in the Python you are running. Try pip install boto3 again.

help me debug my booking engine import error

Your engine now prevents double-booking at the database level. Next up, you will build the full idempotency layer to prevent duplicate operations and add TTL-based reservation expiry.

Implement Idempotency and Reservation Expiry

Network retries, impatient users double-clicking, and mobile apps re-sending requests on reconnect are a fact of life in distributed systems.

Without protection, a single booking request that gets retried could result in multiple charges or conflicting seat holds. In this step, you'll build an idempotency layer that guarantees every booking operation executes exactly once, and you'll understand how application-level expiry keeps held seats from being locked forever.

In this step, get ready to:
  • Build an IdempotencyStore class that atomically claims keys using conditional writes.
  • Walk through the application-level reservation expiry logic already wired into the booking engine.
  • Test both idempotency and expiry by running duplicate requests and observing hold timeouts.
Implement idempotency keys

An idempotency key is a unique token attached to a request. If the same key arrives twice, the system returns the original result instead of executing the operation again.

Your booking_engine.py already calls IdempotencyStore in its hold_seat() method. Now you need to write the class that powers it.

  • In VS Code, open idempotency.py inside your cloudticket/ folder.
  • Add the imports, class definition, and the check_and_claim() method by pasting the following code:
import time
import json
import boto3
from botocore.exceptions import ClientError


class IdempotencyStore:
    """Prevents duplicate booking operations using conditional writes."""

    def __init__(self, table_name="CloudTicket-Idempotency", region="us-east-1"):
        self.table = boto3.resource("dynamodb", region_name=region).Table(table_name)
        self.ttl_seconds = 86400  # 24 hours

    def check_and_claim(self, idempotency_key):
        """
        Attempt to claim an idempotency key.
        Returns (is_new, stored_result):
            - (True, None) if the key was successfully claimed (proceed with operation)
            - (False, result) if the key already exists (return stored result)
        """
        # First, try to read existing result
        try:
            response = self.table.get_item(Key={"idempotency_key": idempotency_key})
            if "Item" in response:
                item = response["Item"]
                if item.get("status") == "COMPLETED":
                    return False, json.loads(item.get("result", "{}"))
                # If status is IN_PROGRESS, another request is handling it
                return False, None
        except ClientError:
            pass

        # Try to claim the key atomically
        try:
            self.table.put_item(
                Item={
                    "idempotency_key": idempotency_key,
                    "status": "IN_PROGRESS",
                    "created_at": int(time.time()),
                    "expires_at": int(time.time()) + self.ttl_seconds,
                    "result": None,
                },
                ConditionExpression="attribute_not_exists(idempotency_key)",
            )
            return True, None
        except ClientError as e:
            if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                # Another process claimed it first, read the result
                response = self.table.get_item(
                    Key={"idempotency_key": idempotency_key}
                )
                if "Item" in response and response["Item"].get("status") == "COMPLETED":
                    return False, json.loads(response["Item"].get("result", "{}"))
                return False, None
            raise

What does this code do?

The check_and_claim() method uses a two-phase approach to guarantee exactly-once execution.

  • Phase 1 (read): Check if the key already exists. If it does and the status is COMPLETED, return the stored result without re-executing.
  • Phase 2 (claim): Use PutItem with attribute_not_exists(idempotency_key) as the ConditionExpression. This ensures only one process can claim the key. If another process beats you, ConditionalCheckFailedException is raised and you fall back to reading the stored result.
  • The expires_at field is set to 24 hours from now. DynamoDB TTL automatically deletes these records after expiry so the table stays clean.
  • Below the check_and_claim() method, add the store_result() method that completes the idempotency cycle:
    def store_result(self, idempotency_key, result):
        """Store the result of a completed operation."""
        self.table.update_item(
            Key={"idempotency_key": idempotency_key},
            UpdateExpression="SET #s = :status, #r = :result",
            ExpressionAttributeNames={"#s": "status", "#r": "result"},
            ExpressionAttributeValues={
                ":status": "COMPLETED",
                ":result": json.dumps(result),
            },
        )

What does this method do?

After the booking operation finishes (success or failure), store_result() updates the idempotency record from IN_PROGRESS to COMPLETED and stores the serialized result. Any future request with the same key reads this stored result and skips execution entirely.

  • Save idempotency.py.

Seeing a syntax error?

  • Make sure store_result is indented at the same level as check_and_claim (both are methods of the IdempotencyStore class).
  • Verify you have import json at the top of the file. The store_result() method calls json.dumps() to serialize the result.

Help me debug a syntax error in my idempotency.py file.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

import time
import json
import boto3
from botocore.exceptions import ClientError


class IdempotencyStore:
    """Prevents duplicate booking operations using conditional writes."""

    def __init__(self, table_name="CloudTicket-Idempotency", region="us-east-1"):
        self.table = boto3.resource("dynamodb", region_name=region).Table(table_name)
        self.ttl_seconds = 86400  # 24 hours

    def check_and_claim(self, idempotency_key):
        """
        Attempt to claim an idempotency key.
        Returns (is_new, stored_result):
            - (True, None) if the key was successfully claimed (proceed with operation)
            - (False, result) if the key already exists (return stored result)
        """
        # First, try to read existing result
        try:
            response = self.table.get_item(Key={"idempotency_key": idempotency_key})
            if "Item" in response:
                item = response["Item"]
                if item.get("status") == "COMPLETED":
                    return False, json.loads(item.get("result", "{}"))
                # If status is IN_PROGRESS, another request is handling it
                return False, None
        except ClientError:
            pass

        # Try to claim the key atomically
        try:
            self.table.put_item(
                Item={
                    "idempotency_key": idempotency_key,
                    "status": "IN_PROGRESS",
                    "created_at": int(time.time()),
                    "expires_at": int(time.time()) + self.ttl_seconds,
                    "result": None,
                },
                ConditionExpression="attribute_not_exists(idempotency_key)",
            )
            return True, None
        except ClientError as e:
            if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                # Another process claimed it first, read the result
                response = self.table.get_item(
                    Key={"idempotency_key": idempotency_key}
                )
                if "Item" in response and response["Item"].get("status") == "COMPLETED":
                    return False, json.loads(response["Item"].get("result", "{}"))
                return False, None
            raise

    def store_result(self, idempotency_key, result):
        """Store the result of a completed operation."""
        self.table.update_item(
            Key={"idempotency_key": idempotency_key},
            UpdateExpression="SET #s = :status, #r = :result",
            ExpressionAttributeNames={"#s": "status", "#r": "result"},
            ExpressionAttributeValues={
                ":status": "COMPLETED",
                ":result": json.dumps(result),
            },
        )
Understand application-level reservation expiry

DynamoDB TTL deletes expired items on a best-effort basis, typically within about two days. For a booking system, that means a seat could stay "held" long after the hold expires if you rely solely on TTL.

Why can't we rely on DynamoDB TTL alone?

DynamoDB TTL is a background cleanup mechanism, not a real-time enforcement mechanism. Expired items may still appear in reads, queries, and scans until TTL actually deletes them.

Production booking systems solve this with a dual approach. The application checks expiry on every read/write (immediate correctness), while TTL handles eventual cleanup (no manual garbage collection needed).

Your booking_engine.py already implements this dual approach in two places. Let's look at both.

  • In booking_engine.py, find the ConditionExpression inside hold_seat():
ConditionExpression=(
    "(booking_status = :available AND attribute_exists(event_id)) OR "
    "(booking_status = :held AND expires_at < :current_time)"
),

What does this condition do?

The first clause (booking_status = :available AND attribute_exists(event_id)) is the normal path. The seat must be available and the item must exist.

The second clause (booking_status = :held AND expires_at < :current_time) is the expiry override. If a seat is still marked HELD but its expires_at timestamp is in the past, another user can claim it. This provides immediate correctness without waiting for TTL cleanup.

  • Now scroll down to the release_expired_holds() method at the bottom of booking_engine.py.
def release_expired_holds(self, event_id):
    """Scan for expired holds and release them back to AVAILABLE."""
    now = int(time.time())
    released = []

    response = self.table.query(
        KeyConditionExpression=boto3.dynamodb.conditions.Key("event_id").eq(
            event_id
        ),
        FilterExpression=(
            boto3.dynamodb.conditions.Attr("booking_status").eq("HELD")
            & boto3.dynamodb.conditions.Attr("expires_at").lt(now)
        ),
    )

Why have both mechanisms?

The ConditionExpression in hold_seat() handles the case where a new user tries to grab an expired seat. The seat transitions directly from stale HELD to newly HELD.

The release_expired_holds() method is a batch cleanup that proactively sets expired seats back to AVAILABLE. In a production system, you would run this on a schedule (every 60 seconds). Together, these two mechanisms ensure no seat stays locked past its expiry.

Test idempotency and expiry

With idempotency.py complete, the full booking engine is now functional. Let's prove that idempotency and expiry both work correctly.

  • Open a terminal in your cloudticket/ folder and start a Python session by running:
python
  • Test idempotency by submitting the same booking request twice with the same key:
from booking_engine import BookingEngine

# Create the engine
engine = BookingEngine()

# Hold a seat with a specific idempotency key
result1 = engine.hold_seat("EVT-001", "SEAT-001", "USER-A", "test-key-001")
print(f"Request 1: {result1.success} - {result1.message}")

# Submit the SAME request again (same idempotency key)
result2 = engine.hold_seat("EVT-001", "SEAT-001", "USER-A", "test-key-001")
print(f"Request 2: {result2.success} - {result2.message}")

# Both should report success, but only ONE write occurred
print(f"Same result? {result1.message == result2.message}")

What should I see?

Both requests should print True for success and show the same message. The second request retrieves the cached result from the CloudTicket-Idempotency table without hitting the bookings table a second time.

  • Now test reservation expiry. Hold a seat with a very short TTL (10 seconds), wait for it to expire, then verify another user can claim it:
import time
import boto3

# Temporarily override hold duration to 10 seconds
engine.HOLD_DURATION_SECONDS = 10

# User B holds SEAT-002
result = engine.hold_seat("EVT-001", "SEAT-002", "USER-B")
print(f"USER-B hold: {result.success}")

# Wait for the hold to expire
print("Waiting 12 seconds for hold to expire...")
time.sleep(12)

# User C tries to claim the same seat (should succeed because hold expired)
result = engine.hold_seat("EVT-001", "SEAT-002", "USER-C")
print(f"USER-C hold after expiry: {result.success} - {result.message}")

What should I see?

USER-B's hold should succeed immediately. After the 12 second wait, USER-C's hold should also succeed because the ConditionExpression detects that the seat's expires_at is in the past and treats it as available.

  • Exit the Python session by typing exit().

Seeing 'Seat is unavailable' for USER-C?

  • Make sure you waited long enough. The hold must expire before USER-C tries to claim it. Increase the sleep to 15 seconds if needed.
  • Check that you set engine.HOLD_DURATION_SECONDS = 10 before the USER-B hold. If the default 300 seconds is still active, the hold won't expire within your wait time.
  • Verify your system clock is accurate. The engine uses time.time() (Unix epoch seconds) for both setting and checking expiry.

Help me debug why my expired hold isn't releasing the seat for another user.

Your booking engine now guarantees exactly-once execution and automatic seat release. Next up, you'll put this engine under serious pressure by simulating 50 concurrent users competing for the same seat.

Build and Run the Concurrency Test Harness

Your booking engine now has all the pieces: a state machine, conditional writes for atomic reservations, idempotency protection, and application-level expiry. But how do you know it actually works under pressure?

In this step, you will build a concurrency test harness that throws 50 simultaneous users at the same seat. If even one double-booking slips through, the test fails. This is how production booking systems prove correctness before going live.

In this step, get ready to:
  • Build a multi-threaded test harness using ThreadPoolExecutor.
  • Run progressively harder concurrency tests (single seat, multi-seat, and idempotency).
  • Generate a test report proving zero double-bookings under load.
Build the multi-threaded test harness

Python's concurrent.futures.ThreadPoolExecutor lets you fire 50 threads at DynamoDB simultaneously. Each thread represents a user trying to grab the same seat at the exact same moment.

  • In VS Code, open the empty test_concurrency.py file inside your cloudticket/ folder.
  • Add the imports and the run_concurrency_test function by pasting this code:
import time
import concurrent.futures
import boto3
from booking_engine import BookingEngine


def run_concurrency_test(num_users, event_id, seat_id):
    """Simulate multiple users trying to book the same seat simultaneously."""
    engine = BookingEngine()
    results = {"success": [], "failure": []}

    def attempt_booking(user_index):
        user_id = f"USER-{user_index:03d}"
        result = engine.hold_seat(event_id, seat_id, user_id)
        return user_id, result

    print(f"\n{'='*60}")
    print(f"CONCURRENCY TEST: {num_users} users competing for {seat_id}")
    print(f"{'='*60}")

    start_time = time.time()

    with concurrent.futures.ThreadPoolExecutor(max_workers=num_users) as executor:
        futures = [
            executor.submit(attempt_booking, i) for i in range(1, num_users + 1)
        ]

        for future in concurrent.futures.as_completed(futures):
            user_id, result = future.result()
            if result.success:
                results["success"].append(user_id)
            else:
                results["failure"].append(user_id)

    elapsed = time.time() - start_time

    # Print results
    print(f"\nResults ({elapsed:.2f}s):")
    print(f"  Successful holds: {len(results['success'])}")
    print(f"  Failed attempts:  {len(results['failure'])}")

    if results["success"]:
        print(f"  Winner(s): {', '.join(results['success'])}")

    # Assertions
    assert len(results["success"]) == 1, (
        f"DOUBLE BOOKING DETECTED! {len(results['success'])} users got the seat!"
    )
    assert len(results["failure"]) == num_users - 1

    print(f"\n  PASS: Zero double-bookings. Exactly 1 winner.")
    return results

What does this code do?

The run_concurrency_test function creates a thread pool with one thread per simulated user. All 50 threads call hold_seat() for the same event and seat simultaneously.

  • Each thread gets a unique user ID (USER-001 through USER-050).
  • Results are collected into success and failure lists.
  • The assertions enforce that exactly 1 user wins and 49 fail. If DynamoDB's conditional writes allow even one extra success, the assertion crashes the test with a clear error.

Why ThreadPoolExecutor instead of asyncio?

Python's asyncio uses cooperative multitasking. A thread pool creates genuine OS-level concurrency where threads race against each other without waiting politely. This simulates real API calls hitting DynamoDB from separate clients simultaneously.

  • Save test_concurrency.py.

Seeing a ModuleNotFoundError?

Make sure you are running from inside the cloudticket/ directory where booking_engine.py lives. The import statement expects booking_engine to be in the same folder.

Help me debug a ModuleNotFoundError when importing booking_engine.

Add the multi-seat and idempotency tests

A single-seat test proves the basic case. But a real system has many seats, and duplicate requests are common when users double-click or retry.

  • Below the run_concurrency_test function in test_concurrency.py, add the multi-seat test function:
def run_multi_seat_test(num_users, event_id, num_seats):
    """Test multiple users competing for multiple seats."""
    engine = BookingEngine()

    print(f"\n{'='*60}")
    print(f"MULTI-SEAT TEST: {num_users} users, {num_seats} seats")
    print(f"{'='*60}")

    all_results = {}

    def attempt_booking(user_index):
        user_id = f"USER-{user_index:03d}"
        seat_id = f"SEAT-{(user_index % num_seats) + 1:03d}"
        result = engine.hold_seat(event_id, seat_id, user_id)
        return user_id, seat_id, result

    start_time = time.time()

    with concurrent.futures.ThreadPoolExecutor(max_workers=num_users) as executor:
        futures = [
            executor.submit(attempt_booking, i) for i in range(1, num_users + 1)
        ]

        for future in concurrent.futures.as_completed(futures):
            user_id, seat_id, result = future.result()
            if seat_id not in all_results:
                all_results[seat_id] = {"success": [], "failure": []}
            if result.success:
                all_results[seat_id]["success"].append(user_id)
            else:
                all_results[seat_id]["failure"].append(user_id)

    elapsed = time.time() - start_time

    # Verify no double bookings per seat
    double_bookings = 0
    for seat_id, seat_results in all_results.items():
        if len(seat_results["success"]) > 1:
            double_bookings += 1
            print(f"  DOUBLE BOOKING on {seat_id}: {seat_results['success']}")

    total_holds = sum(len(r["success"]) for r in all_results.values())
    print(f"\nResults ({elapsed:.2f}s):")
    print(f"  Total successful holds: {total_holds}")
    print(f"  Double bookings: {double_bookings}")

    assert double_bookings == 0, "DOUBLE BOOKINGS DETECTED!"
    print(f"\n  PASS: Zero double-bookings across all seats.")

What does this code do?

This test distributes 50 users across 10 seats using modulo arithmetic (user_index % num_seats). Multiple users compete for the same seat, but the assertion checks that each seat has at most one holder.

It collects per-seat results and verifies zero double-bookings across the entire batch. This mirrors a real event where hundreds of users grab different seats simultaneously.

  • Save test_concurrency.py.
  • Below run_multi_seat_test, add the idempotency test function:
def run_idempotency_test(event_id, seat_id):
    """Test that duplicate requests with the same key produce one hold."""
    engine = BookingEngine()

    print(f"\n{'='*60}")
    print(f"IDEMPOTENCY TEST: 5 duplicate requests, same idempotency key")
    print(f"{'='*60}")

    idempotency_key = "test-idempotency-key-001"
    user_id = "USER-IDEM"
    results = []

    for i in range(5):
        result = engine.hold_seat(event_id, seat_id, user_id, idempotency_key)
        results.append(result)

    successes = [r for r in results if r.success]
    print(f"\n  Total requests: 5")
    print(f"  Successful results: {len(successes)}")

    # All should report success (the original + cached results)
    # But only ONE actual write should have occurred
    table = boto3.resource("dynamodb", region_name="us-east-1").Table(
        "CloudTicket-Bookings"
    )
    item = table.get_item(Key={"event_id": event_id, "seat_id": seat_id})["Item"]
    assert item["held_by"] == user_id
    assert item["booking_status"] == "HELD"

    print(f"  Seat held by: {item['held_by']}")
    print(f"  Version: {item['version']} (should be 1, proving single write)")
    print(f"\n  PASS: Idempotency prevents duplicate operations.")

What does this code do?

This test sends the same booking request 5 times with an identical idempotency key. The IdempotencyStore should ensure only one actual DynamoDB write occurs.

The proof is in the version field. If 5 writes happened, the version would be 5. A version of 1 proves that only the first request executed the write. All subsequent requests returned the cached result.

  • Save test_concurrency.py.

Confused about the version assertion?

The version attribute starts at 0 in the seed data. Each successful hold_seat() call increments it by 1. If 5 duplicate requests each triggered a write, the version would be 5. Version 1 confirms the idempotency layer blocked 4 of the 5 writes.

Explain how the version field proves idempotency is working.

Wire up the test runner and execute all tests

The main block ties all three tests together. It resets the seed data between tests so each one starts from a clean slate.

  • At the bottom of test_concurrency.py, add the main execution block:
if __name__ == "__main__":
    # Reset seed data before tests
    print("Resetting test data...")
    from seed_data import seed_event_data
    seed_event_data()

    event_id = "EVT-001"

    # Test 1: 50 users, 1 seat
    run_concurrency_test(50, event_id, "SEAT-001")

    # Test 2: Reset and run multi-seat test
    seed_event_data()
    run_multi_seat_test(50, event_id, 10)

    # Test 3: Reset and run idempotency test
    seed_event_data()
    run_idempotency_test(event_id, "SEAT-005")

    print(f"\n{'='*60}")
    print("ALL TESTS PASSED - Zero double-bookings detected")
    print(f"{'='*60}")

What does this code do?

  • seed_event_data() resets all 10 seats to AVAILABLE before each test, ensuring a clean starting state.
  • Test 1 throws 50 users at a single seat (SEAT-001). Exactly 1 must win.
  • Test 2 distributes 50 users across 10 seats. Each seat can have at most 1 holder.
  • Test 3 sends 5 duplicate requests for SEAT-005 to verify only 1 DynamoDB write occurs.
  • Save test_concurrency.py.
  • Run the full test suite by executing this command from inside your cloudticket/ directory:
python test_concurrency.py

You should see output confirming all three tests pass, ending with ALL TESTS PASSED - Zero double-bookings detected.

What should I see?

The output starts by re-seeding 10 seats. Then the concurrency test reports 1 successful hold and 49 failures. The multi-seat test shows successful holds across multiple seats with 0 double-bookings. The idempotency test shows version 1, proving a single write.

The exact winner user ID varies each run because thread scheduling is non-deterministic. That is expected.

Tests failing or seeing unexpected errors?

  • If you see throttling errors, your DynamoDB table's provisioned capacity may be too low for 50 simultaneous writes. Check that both tables use 5 RCU / 5 WCU. DynamoDB has burst capacity that handles short spikes.
  • If the idempotency test shows version > 1, make sure you re-seeded the data. The seed_event_data() call before Test 3 resets the version back to 0.
  • If you see NoCredentialsError, confirm your AWS credentials are configured by running aws sts get-caller-identity.

Help me debug my concurrency test failures.

✔️ Awesome, I've got everything!

Great. Double check you have saved test_concurrency.py and that all three tests print PASS.

ⓧ I'd like to double check the full code

import time
import concurrent.futures
import boto3
from booking_engine import BookingEngine


def run_concurrency_test(num_users, event_id, seat_id):
    """Simulate multiple users trying to book the same seat simultaneously."""
    engine = BookingEngine()
    results = {"success": [], "failure": []}

    def attempt_booking(user_index):
        user_id = f"USER-{user_index:03d}"
        result = engine.hold_seat(event_id, seat_id, user_id)
        return user_id, result

    print(f"\n{'='*60}")
    print(f"CONCURRENCY TEST: {num_users} users competing for {seat_id}")
    print(f"{'='*60}")

    start_time = time.time()

    with concurrent.futures.ThreadPoolExecutor(max_workers=num_users) as executor:
        futures = [
            executor.submit(attempt_booking, i) for i in range(1, num_users + 1)
        ]

        for future in concurrent.futures.as_completed(futures):
            user_id, result = future.result()
            if result.success:
                results["success"].append(user_id)
            else:
                results["failure"].append(user_id)

    elapsed = time.time() - start_time

    # Print results
    print(f"\nResults ({elapsed:.2f}s):")
    print(f"  Successful holds: {len(results['success'])}")
    print(f"  Failed attempts:  {len(results['failure'])}")

    if results["success"]:
        print(f"  Winner(s): {', '.join(results['success'])}")

    # Assertions
    assert len(results["success"]) == 1, (
        f"DOUBLE BOOKING DETECTED! {len(results['success'])} users got the seat!"
    )
    assert len(results["failure"]) == num_users - 1

    print(f"\n  PASS: Zero double-bookings. Exactly 1 winner.")
    return results


def run_multi_seat_test(num_users, event_id, num_seats):
    """Test multiple users competing for multiple seats."""
    engine = BookingEngine()

    print(f"\n{'='*60}")
    print(f"MULTI-SEAT TEST: {num_users} users, {num_seats} seats")
    print(f"{'='*60}")

    all_results = {}

    def attempt_booking(user_index):
        user_id = f"USER-{user_index:03d}"
        seat_id = f"SEAT-{(user_index % num_seats) + 1:03d}"
        result = engine.hold_seat(event_id, seat_id, user_id)
        return user_id, seat_id, result

    start_time = time.time()

    with concurrent.futures.ThreadPoolExecutor(max_workers=num_users) as executor:
        futures = [
            executor.submit(attempt_booking, i) for i in range(1, num_users + 1)
        ]

        for future in concurrent.futures.as_completed(futures):
            user_id, seat_id, result = future.result()
            if seat_id not in all_results:
                all_results[seat_id] = {"success": [], "failure": []}
            if result.success:
                all_results[seat_id]["success"].append(user_id)
            else:
                all_results[seat_id]["failure"].append(user_id)

    elapsed = time.time() - start_time

    # Verify no double bookings per seat
    double_bookings = 0
    for seat_id, seat_results in all_results.items():
        if len(seat_results["success"]) > 1:
            double_bookings += 1
            print(f"  DOUBLE BOOKING on {seat_id}: {seat_results['success']}")

    total_holds = sum(len(r["success"]) for r in all_results.values())
    print(f"\nResults ({elapsed:.2f}s):")
    print(f"  Total successful holds: {total_holds}")
    print(f"  Double bookings: {double_bookings}")

    assert double_bookings == 0, "DOUBLE BOOKINGS DETECTED!"
    print(f"\n  PASS: Zero double-bookings across all seats.")


def run_idempotency_test(event_id, seat_id):
    """Test that duplicate requests with the same key produce one hold."""
    engine = BookingEngine()

    print(f"\n{'='*60}")
    print(f"IDEMPOTENCY TEST: 5 duplicate requests, same idempotency key")
    print(f"{'='*60}")

    idempotency_key = "test-idempotency-key-001"
    user_id = "USER-IDEM"
    results = []

    for i in range(5):
        result = engine.hold_seat(event_id, seat_id, user_id, idempotency_key)
        results.append(result)

    successes = [r for r in results if r.success]
    print(f"\n  Total requests: 5")
    print(f"  Successful results: {len(successes)}")

    # All should report success (the original + cached results)
    # But only ONE actual write should have occurred
    table = boto3.resource("dynamodb", region_name="us-east-1").Table(
        "CloudTicket-Bookings"
    )
    item = table.get_item(Key={"event_id": event_id, "seat_id": seat_id})["Item"]
    assert item["held_by"] == user_id
    assert item["booking_status"] == "HELD"

    print(f"  Seat held by: {item['held_by']}")
    print(f"  Version: {item['version']} (should be 1, proving single write)")
    print(f"\n  PASS: Idempotency prevents duplicate operations.")


if __name__ == "__main__":
    # Reset seed data before tests
    print("Resetting test data...")
    from seed_data import seed_event_data
    seed_event_data()

    event_id = "EVT-001"

    # Test 1: 50 users, 1 seat
    run_concurrency_test(50, event_id, "SEAT-001")

    # Test 2: Reset and run multi-seat test
    seed_event_data()
    run_multi_seat_test(50, event_id, 10)

    # Test 3: Reset and run idempotency test
    seed_event_data()
    run_idempotency_test(event_id, "SEAT-005")

    print(f"\n{'='*60}")
    print("ALL TESTS PASSED - Zero double-bookings detected")
    print(f"{'='*60}")

Secret mission

Implement Retry-with-Backoff and a Waiting List

Your booking engine handles concurrency perfectly. But what happens when a user loses out on a seat? In production systems like Ticketmaster, failed users join a waiting list and get automatically promoted when a seat frees up. Build that.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses DynamoDB tables provisioned at 5 RCU / 5 WCU each, which stays well within the free tier (25 WCU / 25 RCU / 25 GB total per region).

Resources you used:

  • DynamoDB table: CloudTicket-Bookings (provisioned: 5 RCU / 5 WCU).
  • DynamoDB table: CloudTicket-Idempotency (provisioned: 5 RCU / 5 WCU).
  • DynamoDB table: CloudTicket-WaitingList (Secret Mission only, provisioned: 5 RCU / 5 WCU).
  • Local project folder: cloudticket/ on your Desktop.

Keep everything running

No action needed. Choose this if you plan to extend CloudTicket with additional features or revisit the concurrency tests.

  • Your three DynamoDB tables persist within the free tier. Combined capacity is 15 RCU / 15 WCU out of the 25 RCU / 25 WCU free tier allowance.
  • As long as you do not create additional provisioned tables that push your total capacity above 25 WCU / 25 RCU, you will not be charged.
  • Your local cloudticket/ folder stays on your Desktop.
  • Python, AWS CLI, and boto3 remain installed on your machine.

Pause - I'll come back to this later

DynamoDB is serverless. There are no running instances to stop or pause. Your tables only consume capacity when requests are made.

  • If you want to reduce capacity while away, you could switch tables to on-demand mode. However, on-demand mode has no free tier, so keeping provisioned mode at 5 RCU / 5 WCU is actually the lowest-cost option.
  • No action required. Your tables sit idle at no cost within free tier limits.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes all data permanently.

  • Delete the CloudTicket-Bookings table by running:
aws dynamodb delete-table --table-name CloudTicket-Bookings
  • Delete the CloudTicket-Idempotency table by running:
aws dynamodb delete-table --table-name CloudTicket-Idempotency
  • If you completed the Secret Mission, delete the CloudTicket-WaitingList table by running:
aws dynamodb delete-table --table-name CloudTicket-WaitingList
  • Delete your local project folder. On macOS/Linux, run:
cd ~/Desktop
rm -rf cloudticket
  • On Windows, run:
cd ~/Desktop
rmdir /s /q cloudticket
  • Verify the tables are gone by running:
aws dynamodb list-tables

You should see no CloudTicket tables in the output.

Nice Work!

Nice Work!

Nice work. You just built a production-grade booking engine that guarantees zero double-bookings under heavy concurrent load.

You've learned how to:

  • Build a concurrency-safe booking engine using DynamoDB conditional writes and optimistic locking to prevent double-booking at the database level.
  • Implement a state machine governing booking lifecycle transitions, an idempotency layer preventing duplicate operations, and dual-layer reservation expiry combining application-level checks with DynamoDB TTL.
  • Prove correctness under load with a concurrency test harness simulating 50 simultaneous users competing for 1 seat, confirming exactly 1 winner and 0 double-bookings.
  • Secret Mission: Add retry-with-backoff for transient errors and a waiting list with automatic promotion when held seats expire.

Ready to quiz yourself?