CASE STUDY

Amazon Locker System (LLD + Backend)

3 min read·572 words·Intermediate

Asked at

2 candidate reports between Jan 2026 and Jul 2026

How to use this case study

SDE-2 / Mid

Design the classes (Locker station, Compartment with size, Package, PickupCode) and the flows for deposit and pickup.

SDE-3 / Senior

Choose compartments (best fit by size), handle concurrency (two couriers, one compartment), code security and expiry of unclaimed packages.

Staff / Principal

Discuss the distributed backend for thousands of stations with flaky connectivity, reservations at order time, capacity planning and operational alerts.


0) Problem Restatement

Design Amazon Locker: self-service stations with many compartments of different sizes (small, medium, large). When a customer chooses a locker for delivery, the system assigns a compartment that fits the package. The courier deposits the package, and the customer gets a one-time pickup code valid for a few days (e.g., 3). If nobody picks it up, the package is returned. Amazon asked it both as class design (one station) and as a distributed system (many stations, multi-size allocation).


1) Requirements

  • Locker stations with compartments of sizes S, M, L (and availability).
  • Assign a compartment that fits the package (smallest size that fits).
  • The courier deposits → the compartment is marked occupied → the customer gets a code (SMS or app).
  • The customer enters the code → the door opens → the compartment is freed.
  • Codes expire. Expired packages go back to the courier for return.
  • Handle two couriers or requests at once without double-assigning a compartment.


2) Class Design

Architecture Diagram

classDiagram
    class LockerStation {
        +String stationId
        +Location location
        +List compartments
        +findCompartment(size) Compartment
        +deposit(packageId, compartmentId) PickupCode
        +pickup(code) Compartment
    }
    class Compartment {
        +String id
        +Size size
        +State state
        +String packageId
    }
    class Package {
        +String id
        +Size size
        +String customerId
    }
    class PickupCode {
        +String codeHash
        +String compartmentId
        +DateTime expiresAt
        +boolean used
    }
    class LockerService {
        +reserve(orderId, stationId, size) Reservation
        +confirmDeposit(reservationId) PickupCode
        +redeem(stationId, code) Compartment
        +expireOld() void
    }
    LockerStation "1" --> "*" Compartment
    LockerService --> LockerStation
    Compartment --> Package
    PickupCode --> Compartment
Compartment states: FREE → RESERVED → OCCUPIED → FREE (plus OUT_OF_SERVICE).

3) Key Logic

3.1 Choosing a compartment (best fit)

Pick the smallest free size that fits (S before M before L), so big compartments stay available for big packages. Only if the fitting size is full, use a bigger one.

SIZES = ["S", "M", "L"]

def find_compartment(station, pkg_size):
    for size in SIZES[SIZES.index(pkg_size):]:          # same size first, then bigger
        for c in station.compartments:
            if c.size == size and c.state == "FREE":
                return c
    return None                                           # station full for this size

(In a real system, keep a free list per size so this is O(1).)

3.2 Concurrency

Two deliveries could pick the same free compartment. Make the state change atomic: UPDATE compartments SET state='RESERVED', reservation_id=? WHERE id=? AND state='FREE'. If 0 rows are updated, someone else took it, so pick another. In a single process, use a lock per station (or per size free list).

3.3 Codes

  • Generate a random 6–8 digit code, and store only its hash with the compartment and expiry.
  • Codes are single-use. After too many wrong attempts, lock the keypad for a while (stops guessing).
  • On pickup, check hash, expiry and used = false, then open the door, and mark the compartment FREE and the code used.


4) Backend for Many Stations

Architecture Diagram

flowchart LR
    CHK["Checkout - choose locker"] --> LS["Locker Service"]
    LS --> DB[("Stations, compartments, reservations")]
    CR["Courier app"] --> LS
    ST["Locker station controller"] <-->|"sync, commands"| LS
    LS --> N["Notifications - SMS, app"]
    JOB["Expiry job"] --> LS
  • Reserve at order time: when the customer picks a locker at checkout, reserve a compartment size for the expected delivery date. This avoids a courier arriving to a full station.
  • Station controllers can lose connectivity. They cache active codes (hashed) locally so pickups still work offline, and sync events (deposits, pickups) when back online.
  • Expiry job: every hour, find OCCUPIED compartments whose code expired. Notify the customer, then create a return task for the courier and free the compartment after removal.


5) Edge Cases

  • Package doesn't fit the assigned compartment: the courier requests a re-assignment to a bigger one on the spot.
  • Door fails to open / compartment broken: mark it OUT_OF_SERVICE, move the package or reassign, and alert operations.
  • Customer changes delivery location: release the reservation.


6) Wrap-Up

Model a station with sized compartments that move through FREE → RESERVED → OCCUPIED → FREE. Assign the smallest free compartment that fits, with an atomic conditional update so two couriers never get the same one. Issue hashed, single-use, expiring pickup codes with attempt limits, reserve capacity at checkout, let station controllers work offline with cached codes, and run an expiry job to return unclaimed packages.

More Case Studies

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →