What L5 looks like
- An explicit parcel lifecycle that covers expiry and both kinds of return, and explains why an expired compartment stays locked until a carrier collects.
- Atomic slot allocation under concurrent deposits, random one-time codes from
secrets, and an injected clock so expiry tests don't sleep. - Uses a strategy for allocation and events for notifications, without over-engineering the state machine.
Requirements
- One locker location has a fixed set of compartments, each
SMALL,MEDIUMorLARGE. - Deliver: a carrier deposits a package. The system picks the smallest free compartment the package fits in, opens its door, and issues a one-time pickup code to the customer.
- Pick up: the customer types the code at the kiosk. If the code is valid and not expired, the compartment opens and the code stops working.
- Expiry: a package not picked up within the hold window (3 days, configurable) expires. Its code stops working, the compartment stays locked, and the package waits for a carrier to take it back to the sender.
- Customer returns: a customer reserves a compartment for an item they are sending back, gets a drop-off code, drops the item off, and a carrier collects it later. If nobody uses a reservation, it expires and frees the compartment.
- Two concurrent deposits must never get the same compartment, and a code maps to exactly one live parcel. A compartment taken out of service (broken door) is never assigned.
- Out of scope: orders, sending notifications (we only emit events), payments, carrier authentication, choosing which location, door hardware.
Core objects
| Object | Responsibility |
|---|---|
Size, SlotState (enums) |
Ordered size, so "fits" means slot.size >= pkg.size; compartment state FREE, OCCUPIED, OUT_OF_SERVICE. |
Compartment |
One physical door: id, size, state. open_door() calls the hardware. |
Package |
The item that goes in: id and size. |
ParcelState (enum) |
Lifecycle of one assignment, for a delivery or a return. |
Assignment |
Binds a package to a compartment, with a code, an expiry and a state. |
FitStrategy (ABC) |
Chooses a compartment for a size. SmallestFit is the default. |
LockerLocation |
Facade for deposits, kiosk codes, return reservations, carrier collection and the expiry sweep. Owns the lock and the indexes; raises LockerError. |
Class design
{"type":"excalidraw","elements":[
{"type":"rectangle","id":"loc","x":0,"y":220,"width":310,"height":180,"label":{"text":"LockerLocation\ncompartments\nby_code, by_package\ndeposit(), reserve_return()\nenter_code(), collect()\nsweep_expired()"}},
{"type":"rectangle","id":"asg","x":520,"y":240,"width":240,"height":130,"label":{"text":"Assignment\npackage, compartment\ncode, expires_at\nstate"}},
{"type":"rectangle","id":"comp","x":960,"y":60,"width":200,"height":110,"label":{"text":"Compartment\nid, size, state\nopen_door()"}},
{"type":"rectangle","id":"pkg","x":960,"y":390,"width":180,"height":90,"label":{"text":"Package\nid, size"}},
{"type":"rectangle","id":"fit","x":45,"y":520,"width":220,"height":110,"label":{"text":"FitStrategy\n(abstract)\nchoose(size, free)"}},
{"type":"rectangle","id":"small","x":440,"y":540,"width":180,"height":70,"label":{"text":"SmallestFit"}},
{"type":"arrow","id":"loc-asg","x":310,"y":310,"width":210,"height":-10,"start":{"id":"loc"},"end":{"id":"asg"},"label":{"text":"has many"}},
{"type":"arrow","id":"loc-comp","x":155,"y":220,"width":805,"height":-105,"start":{"id":"loc"},"end":{"id":"comp"},"points":[[0,0],[0,-105],[805,-105]],"label":{"text":"has many"}},
{"type":"arrow","id":"asg-comp","x":760,"y":265,"width":200,"height":-125,"start":{"id":"asg"},"end":{"id":"comp"},"label":{"text":"has"}},
{"type":"arrow","id":"asg-pkg","x":760,"y":345,"width":200,"height":90,"start":{"id":"asg"},"end":{"id":"pkg"},"label":{"text":"has"}},
{"type":"arrow","id":"loc-fit","x":155,"y":400,"width":1,"height":120,"start":{"id":"loc"},"end":{"id":"fit"},"label":{"text":"uses"}},
{"type":"arrow","id":"small-fit","x":440,"y":575,"width":-175,"height":0,"start":{"id":"small"},"end":{"id":"fit"},"label":{"text":"extends"}}
]}
Key flows
1. Deposit (carrier). deposit(pkg) takes the lock and filters the FREE compartments. FitStrategy.choose picks one, which is marked OCCUPIED. A unique 6-digit code is generated, and an Assignment(READY) with expires_at = now + hold is indexed by code and by package id. After the lock is released, the door opens and a ready event fires; the notifier sends the code to the customer.
2. Kiosk code (customer). enter_code(code) looks up the assignment in the live-code index (door I/O and events always run after the lock is released). It must be READY or AWAITING_DROPOFF, and now < expires_at. That time check matters because the sweep may not have run yet. The code is removed, since it works only once. A READY parcel becomes PICKED_UP and frees its compartment. A return becomes DROPPED_OFF and keeps the compartment for the carrier. The door opens.
3. Expiry sweep (scheduler, every few minutes). sweep_expired() moves each overdue READY parcel to EXPIRED and removes its code. The compartment stays OCCUPIED, and a return_to_sender event goes to the carriers. An unused return reservation that is overdue becomes CANCELLED and frees its compartment.
4. Carrier collection. collect(package_id) works for EXPIRED and DROPPED_OFF parcels. It opens the door, marks the parcel COLLECTED and frees the compartment. A return starts with reserve_return(pkg), which assigns a compartment like a deposit but in state AWAITING_DROPOFF, without opening the door.
{"type":"excalidraw","elements":[
{"type":"ellipse","id":"start","x":0,"y":215,"width":100,"height":60,"label":{"text":"start"}},
{"type":"rectangle","id":"ready","x":300,"y":100,"width":280,"height":70,"label":{"text":"READY"}},
{"type":"rectangle","id":"await","x":300,"y":380,"width":280,"height":70,"label":{"text":"AWAITING_DROPOFF"}},
{"type":"rectangle","id":"picked","x":840,"y":-60,"width":200,"height":70,"label":{"text":"PICKED_UP"}},
{"type":"rectangle","id":"expired","x":840,"y":100,"width":200,"height":70,"label":{"text":"EXPIRED"}},
{"type":"rectangle","id":"dropped","x":840,"y":380,"width":200,"height":70,"label":{"text":"DROPPED_OFF"}},
{"type":"rectangle","id":"cancelled","x":840,"y":540,"width":200,"height":70,"label":{"text":"CANCELLED"}},
{"type":"rectangle","id":"collected","x":1300,"y":240,"width":200,"height":70,"label":{"text":"COLLECTED"}},
{"type":"arrow","id":"deposit","x":100,"y":230,"width":200,"height":-95,"start":{"id":"start"},"end":{"id":"ready"},"label":{"text":"deposit"}},
{"type":"arrow","id":"reserve","x":100,"y":260,"width":200,"height":155,"start":{"id":"start"},"end":{"id":"await"},"label":{"text":"reserve_return"}},
{"type":"arrow","id":"r-pick","x":580,"y":115,"width":260,"height":-140,"start":{"id":"ready"},"end":{"id":"picked"},"label":{"text":"enter_code"}},
{"type":"arrow","id":"r-exp","x":580,"y":145,"width":260,"height":0,"start":{"id":"ready"},"end":{"id":"expired"},"label":{"text":"sweep, hold passed"}},
{"type":"arrow","id":"e-col","x":1040,"y":135,"width":260,"height":125,"start":{"id":"expired"},"end":{"id":"collected"},"label":{"text":"carrier collect"}},
{"type":"arrow","id":"a-drop","x":580,"y":405,"width":260,"height":0,"start":{"id":"await"},"end":{"id":"dropped"},"label":{"text":"enter_code"}},
{"type":"arrow","id":"a-cancel","x":580,"y":435,"width":260,"height":140,"start":{"id":"await"},"end":{"id":"cancelled"},"label":{"text":"sweep, unused"}},
{"type":"arrow","id":"d-col","x":1040,"y":415,"width":260,"height":-125,"start":{"id":"dropped"},"end":{"id":"collected"},"label":{"text":"carrier collect"}}
]}
Code
import secrets, threading
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum, IntEnum
from typing import Callable, Optional
Size = IntEnum("Size", "SMALL MEDIUM LARGE") # ordered: a slot fits if slot.size >= pkg.size
SlotState = Enum("SlotState", "FREE OCCUPIED OUT_OF_SERVICE")
ParcelState = Enum("ParcelState",
"READY PICKED_UP EXPIRED AWAITING_DROPOFF DROPPED_OFF COLLECTED CANCELLED")
class LockerError(Exception): ...
@dataclass
class Compartment:
id: str
size: Size
state: SlotState = SlotState.FREE
def open_door(self) -> None: print(f"open {self.id}") # hardware call in a real kiosk
@dataclass(frozen=True)
class Package:
id: str
size: Size
@dataclass
class Assignment:
package: Package
compartment: Compartment
code: str
expires_at: datetime
state: ParcelState
class FitStrategy(ABC):
@abstractmethod
def choose(self, size: Size, free: list[Compartment]) -> Optional[Compartment]: ...
class SmallestFit(FitStrategy):
def choose(self, size: Size, free: list[Compartment]) -> Optional[Compartment]:
fits = [c for c in free if c.size >= size]
return min(fits, key=lambda c: c.size, default=None)
class LockerLocation:
def __init__(self, compartments: list[Compartment], strategy: FitStrategy = SmallestFit(),
hold: timedelta = timedelta(days=3), clock: Callable[[], datetime] = datetime.now,
on_event: Callable[[str, Assignment], None] = lambda e, a: None) -> None:
self._compartments = compartments
self._strategy, self._hold, self._clock, self._emit = strategy, hold, clock, on_event
self._lock = threading.Lock()
self._by_code: dict[str, Assignment] = {} # live, unused codes only
self._by_package: dict[str, Assignment] = {} # every assignment holding a slot
def deposit(self, package: Package) -> Assignment:
return self._assign(package, ParcelState.READY, open_now=True)
def reserve_return(self, package: Package) -> Assignment:
return self._assign(package, ParcelState.AWAITING_DROPOFF, open_now=False)
def enter_code(self, code: str) -> Assignment:
with self._lock:
a = self._by_code.get(code)
if a is None or self._clock() >= a.expires_at:
raise LockerError("invalid or expired code")
del self._by_code[code] # one-time code
if a.state is ParcelState.READY:
a.state = ParcelState.PICKED_UP
self._release(a)
else:
a.state = ParcelState.DROPPED_OFF # slot stays held for the carrier
state = a.state
a.compartment.open_door()
self._emit(state.name.lower(), a)
return a
def collect(self, package_id: str) -> Package:
with self._lock:
a = self._by_package.get(package_id)
if a is None or a.state not in (ParcelState.EXPIRED, ParcelState.DROPPED_OFF):
raise LockerError("nothing to collect")
a.state = ParcelState.COLLECTED
self._release(a)
a.compartment.open_door()
self._emit("collected", a)
return a.package
def sweep_expired(self) -> list[Assignment]:
changed: list[Assignment] = []
with self._lock:
for a in list(self._by_package.values()):
if self._clock() < a.expires_at or a.code not in self._by_code:
continue # not due, or code already used
if a.state is ParcelState.READY:
a.state = ParcelState.EXPIRED # stays locked until a carrier collects
del self._by_code[a.code]
else:
a.state = ParcelState.CANCELLED
self._release(a)
changed.append(a)
for a in changed:
self._emit("return_to_sender" if a.state is ParcelState.EXPIRED else "cancelled", a)
return changed
def _assign(self, package: Package, state: ParcelState, open_now: bool) -> Assignment:
with self._lock:
if package.id in self._by_package:
raise LockerError(f"package {package.id} already assigned")
free = [c for c in self._compartments if c.state is SlotState.FREE]
slot = self._strategy.choose(package.size, free)
if slot is None:
raise LockerError("no free compartment fits this package")
slot.state = SlotState.OCCUPIED
a = Assignment(package, slot, self._new_code(), self._clock() + self._hold, state)
self._by_code[a.code] = self._by_package[package.id] = a
if open_now:
slot.open_door()
self._emit(state.name.lower(), a)
return a
def _release(self, a: Assignment) -> None: # caller holds the lock
if a.compartment.state is SlotState.OCCUPIED: # never revive OUT_OF_SERVICE
a.compartment.state = SlotState.FREE
self._by_code.pop(a.code, None)
self._by_package.pop(a.package.id, None)
def _new_code(self) -> str: # caller holds the lock
while True:
code = f"{secrets.randbelow(10**6):06d}"
if code not in self._by_code:
return code
if __name__ == "__main__":
loc = LockerLocation([Compartment("S1", Size.SMALL), Compartment("M1", Size.MEDIUM),
Compartment("L1", Size.LARGE)])
a, b = loc.deposit(Package("p1", Size.MEDIUM)), loc.deposit(Package("p2", Size.MEDIUM))
assert (a.compartment.id, b.compartment.id) == ("M1", "L1") # smallest fit, then next
assert loc.enter_code(a.code).state is ParcelState.PICKED_UP # M1 is free again
r = loc.reserve_return(Package("ret1", Size.SMALL))
loc.enter_code(r.code) # customer drops the item in S1
assert loc.collect("ret1").id == "ret1"
Design patterns
-
Strategy:
FitStrategykeeps "which compartment" out of the locker.SmallestFitkeeps large doors free for large parcels. Other rules, such as spreading wear across doors or keeping someLARGEfree, plug in without changingLockerLocation. -
Facade: the kiosk, the carrier app and the scheduler all call
LockerLocation. Nothing else changes compartment state, so the invariants live in one place. -
State (as an enum state machine): each operation checks the
ParcelStatefirst, for examplecollectneedsEXPIREDorDROPPED_OFF. With this few transitions, an enum check is clearer than one class per state. -
Observer: the
on_eventcallback separates SMS and email, carrier dispatch and metrics from the core flows.
Extensions
-
Many locations, "find me a locker": a
LockerNetworkholdsLockerLocations and asks the ones near the customer for a fit. Reserve the compartment when the order ships (a newAWAITING_DELIVERYstate with its own expiry), not when the carrier arrives. Each location has its own lock, so they scale independently. -
Persistence, several kiosks per site: store compartments and assignments in a database. Replace the in-process lock with a conditional update (
UPDATE compartment SET state='OCCUPIED' WHERE id=? AND state='FREE') and a unique index on live codes. -
Hold extensions and per-item windows: add
extend(code, days), which movesexpires_atwhile the parcel isREADY. Turn the fixed hold into aHoldPolicy.window(package), for example a short window for refrigerated goods.
Interviewer expectations
Mid-level (L4)
- Clean classes for compartment, package, size and locker, with a working "smallest compartment that fits" rule.
- Generates and checks the pickup code, frees the compartment after pickup, and handles no fit, a wrong code and a reused code.
Senior (L5)
- An explicit parcel lifecycle that covers expiry and both kinds of return, and explains why an expired compartment stays locked until a carrier collects.
- Atomic slot allocation under concurrent deposits, random one-time codes from
secrets, and an injected clock so expiry tests don't sleep. - Uses a strategy for allocation and events for notifications, without over-engineering the state machine.
Staff (L6+)
- Takes the design to a database: conditional updates instead of locks, idempotent carrier calls, and recovery when the door fails to open after a commit.
- Reasons about capacity: reserving at ship time, overbooking risk, the size mix per location, and a backlog when sweeps fall behind.
- Covers security and operations: brute-force limits on codes, an audit trail per door, and handling a broken compartment with a package inside.
Make it yours
Write each chapter yourself, check it against this answer, then let the coach grill you.
