Common Problems
Amazon Locker
Watch Video Walkthrough
Watch the author walk through the problem step-by-step
Watch Video Walkthrough
Watch the author walk through the problem step-by-step
Understanding the Problem

Requirements
"Design a locker system like Amazon Locker where delivery drivers can deposit packages and customers can pick them up using a code."
Clarifying Questions
You: "I haven't used an Amazon locker, can you explain how it works?"Interviewer: "It's a self-serve pickup point. When you order, you can choose a locker location. Once the package is deposited, you get a code to open the locker and grab your package."
You: "Are there different sized compartments? Can a small package go into a large compartment if all the small ones are full?"Interviewer: "Yes, we have small, medium, and large compartments. For now, match the size exactly. If there's no compartment of the right size, reject the deposit."
You: "What's in scope for this system? Are we modeling the whole delivery flow, or just the piece from when the driver arrives at the locker until the customer picks up?"Interviewer: "Just the locker operations. Assume the package is already at the locker location. Delivery routing and driver assignment are out of scope."
You: "How does the customer get their code? Do we need to send an SMS or email, or do we just return the code and let some other system handle notification?"Interviewer: "Return the code. How it gets to the customer is someone else's problem."
You: "What happens if someone enters the wrong code multiple times? Should we lock them out for security?"Interviewer: "Interesting question, but let's keep it simple. Just validate the code. If it's wrong, return an error. No lockout logic for now."
You: "Can one customer have multiple packages in the system at once? Are access tokens unique per package?"Interviewer: "One access token per package. A customer could have multiple packages, and each would get its own code and be stored in its own compartment."
You: "How long do the codes last? What happens to a package if it's never picked up?"Interviewer: "Codes expire after 7 days. If someone tries to use an expired code, we reject it. The package stays in the compartment until staff removes it and returns it to the sender."
You: "Last one. What if all compartments of a given size are full when a driver tries to deposit?"Interviewer: "Return an error. The driver would need to try a different locker location or come back later."
Final Requirements
Requirements:
1. Carrier deposits a package by specifying size (small, medium, large)
- System assigns an available compartment of matching size
- Opens compartment and returns access token, or error if no space
2. Upon successful deposit, an access token is generated and returned
- One access token per package
3. User retrieves package by entering access token
- System validates code and opens compartment
- Throws specific error if code is invalid or expired
4. Access tokens expire after 7 days
- Expired codes are rejected if used for pickup
- Package remains in compartment until staff removes it
5. Staff can open all expired compartments to manually handle packages
- System opens all compartments with expired tokens
- Staff physically removes packages and returns them to sender
6. Invalid access tokens are rejected with clear error messages
- Wrong code, already used, or expired - user gets specific feedback
Out of scope:
- How the package gets to the locker (delivery logistics)
- How the access token reaches the customer (SMS/email notification)
- Lockout after failed access token attempts
- UI/rendering layer
- Multiple locker stations
- Payment or pricingCore Entities and Relationships
| Entity | Responsibility |
|---|---|
| Locker | The orchestrator. Owns all compartments and the AccessToken lookup map. Handles deposit and pickup operations. |
| AccessToken | Represents a bearer token for compartment access. Holds the code, expiration timestamp, and a reference to the compartment it unlocks. Enforces expiry when validating. |
| Compartment | A physical locker slot. Has an ID, a size, and tracks its own occupancy state (whether a package is physically present). |
Class Design
Locker
| Requirement | What Locker must track |
|---|---|
| "System assigns an available compartment of matching size" | The collection of all compartments and which ones are occupied |
| "User retrieves package by entering access token" | A map from access token code to AccessToken object for fast lookup |
class Locker:
- compartments: Compartment[]
- accessTokenMapping: Map<string, AccessToken>| Need from requirements | Method on Locker |
|---|---|
| "Carrier deposits a package by specifying size" | depositPackage(size) opens compartment and returns access token code |
| "Upon successful deposit, an access token is generated and returned" | A way to generate and look up access tokens |
| "User retrieves package by entering access token" | pickup(tokenCode) opens compartment or throws error |
| "Staff can open all expired compartments to manually handle packages" | openExpiredCompartments() opens all compartments with expired tokens |
class Locker:
- compartments: Compartment[]
- accessTokenMapping: Map<string, AccessToken>
+ Locker(compartments)
+ depositPackage(size) -> string | error
+ pickup(tokenCode) -> void | error
+ openExpiredCompartments() -> voidAccessToken
| Requirement | What AccessToken must track |
|---|---|
| "An access token is generated and returned" | The actual code string |
| "Access tokens expire after 7 days" | An expiration timestamp |
| "System validates code and opens compartment" | A reference to the compartment this access token unlocks |
class AccessToken:
- code: string
- expiration: timestamp
- compartment: Compartmentclass AccessToken:
- code: string
- expiration: timestamp
- compartment: Compartment
+ AccessToken(code, expiration, compartment)
+ isExpired() -> boolean
+ getCompartment() -> Compartment
+ getCode() -> stringCompartment
| Requirement | What Compartment must track |
|---|---|
| "System assigns an available compartment of matching size" | Size (small, medium, large) |
class Compartment:
- size: Size
- occupied: booleanclass Compartment:
- size: Size
- occupied: boolean
+ Compartment(size)
+ getSize() -> Size
+ isOccupied() -> boolean
+ markOccupied() -> void
+ markFree() -> void
+ open() -> voidenum Size:
SMALL
MEDIUM
LARGEFinal Class Design
class Locker:
- compartments: Compartment[]
- accessTokenMapping: Map<string, AccessToken>
+ Locker(compartments)
+ depositPackage(size) -> string | error
+ pickup(tokenCode) -> void | error
+ openExpiredCompartments() -> void
class AccessToken:
- code: string
- expiration: timestamp
- compartment: Compartment
+ AccessToken(code, expiration, compartment)
+ isExpired() -> boolean
+ getCompartment() -> Compartment
+ getCode() -> string
class Compartment:
- size: Size
- occupied: boolean
+ Compartment(size)
+ getSize() -> Size
+ isOccupied() -> boolean
+ markOccupied() -> void
+ markFree() -> void
+ open() -> void
enum Size:
SMALL
MEDIUM
LARGEImplementation
- Define the core logic - The happy path that fulfills the requirement
- Handle edge cases - Invalid inputs, boundary conditions, unexpected states
- Locker.depositPackage - shows the allocation logic and how we tie together compartments and access tokens
- Locker.pickup - shows the validation flow and cleanup
- AccessToken.getCompartmentIfValid - shows how AccessToken enforces expiry
Locker
- Find an available compartment of the requested size
- Generate an access token for that compartment
- Mark the compartment as occupied in the occupancy set
- Store the access token in the lookup map
- Return both the compartment ID and access token code
- No compartment available of the requested size
- Invalid size parameter
depositPackage(size)
compartment = getAvailableCompartment(size)
if compartment == null
throw Error("No available compartment of size " + size)
compartment.open()
compartment.markOccupied()
accessToken = generateAccessToken(compartment)
accessTokenMapping[accessToken.getCode()] = accessToken
return accessToken.getCode()- Look up the access token by code
- Validate the token (check expiry)
- If valid, open the compartment and clean up
- If invalid (expired or doesn't exist), throw a specific error
- Access token doesn't exist in the map
- Access token exists but is expired
- Access token code is null or empty
pickup(tokenCode)
if tokenCode == null || tokenCode.isEmpty()
throw Error("Invalid access token code")
accessToken = accessTokenMapping[tokenCode]
if accessToken == null
throw Error("Invalid access token code")
if accessToken.isExpired()
throw Error("Access token has expired")
// Valid pickup - unlock door and clean up
compartment = accessToken.getCompartment()
compartment.open()
clearDeposit(accessToken)generateAccessToken(compartment)
code = generateRandomCode() // 6-digit number, UUID, whatever
expiration = now() + 7.days()
return AccessToken(code, expiration, compartment)clearDeposit(accessToken)
compartment = accessToken.getCompartment()
compartment.markFree()
accessTokenMapping.remove(accessToken.getCode())openExpiredCompartments()
for tokenCode, accessToken in accessTokenMapping
if accessToken.isExpired()
compartment = accessToken.getCompartment()
compartment.open()AccessToken
- isExpired checks if current time is past expiration
- getCompartment returns the compartment reference
- Expiration timestamp is in the past
isExpired()
return now() >= expiration
getCompartment()
return compartment
getCode()
return codeCompartment
getSize()
return size
isOccupied()
return occupied
markOccupied()
occupied = true
markFree()
occupied = falseVerification
Initial: compartments=[A, B, C], accessTokenMapping={}
getAvailableCompartment(MEDIUM) → Compartment B (size matches, not occupied)
B.open() → triggers hardware unlock
B.markOccupied() → B.occupied = true
generateAccessToken(B) → AccessToken("ABC123", expiration=now+7days, compartment=B)
accessTokenMapping.put("ABC123", accessToken) → map now has entry
Result: "ABC123"
State: B.occupied=true, accessTokenMapping={"ABC123" → AccessToken}accessTokenMapping.get("ABC123") → AccessToken exists
accessToken.isExpired() → now < expiration, returns false
accessToken.getCompartment() → returns B
B.open() → triggers hardware unlock
clearDeposit(accessToken):
- accessToken.getCompartment() → returns B
- B.markFree() → B.occupied = false
- accessTokenMapping.remove("ABC123")
Result: void (compartment opened)
State: B.occupied=false, accessTokenMapping={}accessTokenMapping.get("ABC123") → AccessToken still exists
accessToken.isExpired() → now > expiration, returns true
throw Error("Access token has expired")
Result: Error thrown (no compartment opened)
State: B.occupied=true, accessTokenMapping={"ABC123" → AccessToken (expired)}Complete Code Implementation
from datetime import datetime, timedelta
from typing import Optional
import random
class Locker:
def __init__(self, compartments: list["Compartment"]):
self.compartments = compartments
self.access_token_mapping: dict[str, "AccessToken"] = {}
def deposit_package(self, size: "Size") -> str:
compartment = self._get_available_compartment(size)
if compartment is None:
raise Exception(f"No available compartment of size {size}")
compartment.open()
compartment.mark_occupied()
access_token = self._generate_access_token(compartment)
self.access_token_mapping[access_token.get_code()] = access_token
return access_token.get_code()
def pickup(self, token_code: str) -> None:
if not token_code:
raise Exception("Invalid access token code")
access_token = self.access_token_mapping.get(token_code)
if access_token is None:
raise Exception("Invalid access token code")
if access_token.is_expired():
raise Exception("Access token has expired")
compartment = access_token.get_compartment()
compartment.open()
self._clear_deposit(access_token)
def open_expired_compartments(self) -> None:
for access_token in self.access_token_mapping.values():
if access_token.is_expired():
compartment = access_token.get_compartment()
compartment.open()
def _get_available_compartment(self, size: "Size") -> Optional["Compartment"]:
for c in self.compartments:
if c.get_size() == size and not c.is_occupied():
return c
return None
def _generate_access_token(self, compartment: "Compartment") -> "AccessToken":
code = f"{random.randint(0, 999999):06d}"
expiration = datetime.now() + timedelta(days=7)
return AccessToken(code, expiration, compartment)
def _clear_deposit(self, access_token: 'AccessToken') -> None:
compartment = access_token.get_compartment()
compartment.mark_free()
self.access_token_mapping.pop(access_token.get_code(), None)
Extensibility
1. "How would you handle size fallback?"
"I'd change getAvailableCompartment to try the exact size first, then fall back to larger sizes if nothing is available. For a MEDIUM package, check MEDIUM first, then LARGE. For SMALL, check SMALL, then MEDIUM, then LARGE. We'd never fall back to a smaller size since the package won't fit.The key change is in the scanning logic. Instead of a single loop checking for exact size match, we iterate through sizes starting from the requested size up to the largest."
getAvailableCompartment(requestedSize)
sizesInOrder = [SMALL, MEDIUM, LARGE]
startIndex = sizesInOrder.indexOf(requestedSize)
for i from startIndex to sizesInOrder.length
size = sizesInOrder[i]
for c in compartments
if c.getSize() == size && !c.isOccupied()
return c
return null // No compartment available2. "How would you handle compartments that are broken or under maintenance?"
"Good catch. We'd need to add a status field to Compartment to track whether it's operational. A compartment could be occupied, available, or out of service. When a compartment is out of service, we skip it during allocation just like we skip occupied ones."
enum CompartmentStatus:
AVAILABLE
OCCUPIED
OUT_OF_SERVICE
class Compartment:
- size: Size
- status: CompartmentStatus
+ isAvailable() -> boolean
+ markOccupied()
+ markAvailable()
+ markOutOfService()
+ markInService()getAvailableCompartment(size)
for compartment in compartments
if compartment.size == size and compartment.isAvailable()
return compartment
return null
// Compartment.isAvailable() implementation
isAvailable()
return status == AVAILABLE3. "How would you ensure packages are actually deposited before generating access tokens?"
"Good point. We're currently using a fire-and-forget approach where compartment.open() unlocks the door and we assume the driver completes the deposit. To verify the package is actually there, we'd need a two-phase commit pattern.Instead of depositPackage doing everything at once, we'd split it into two operations: reserveCompartment and confirmDeposit. The driver would call reserve to get a compartment and unlock it, physically place the package, then call confirm. Only after confirmation would we generate the access token and mark it occupied."
class Locker:
+ reserveCompartment(size) -> reservationId
+ confirmDeposit(reservationId) -> tokenCode
+ cancelReservation(reservationId) -> void
class Compartment:
- size: Size
- status: CompartmentStatus // AVAILABLE, RESERVED, OCCUPIED
enum CompartmentStatus:
AVAILABLE
RESERVED
OCCUPIED
OUT_OF_SERVICEreserveCompartment(size)
compartment = getAvailableCompartment(size)
if compartment == null
throw Error("No available compartment")
compartment.markReserved()
compartment.open()
reservationId = generateReservationId()
reservationMapping[reservationId] = compartment
return reservationId
confirmDeposit(reservationId)
compartment = reservationMapping[reservationId]
if compartment == null
throw Error("Invalid reservation")
compartment.markOccupied()
accessToken = generateAccessToken(compartment)
accessTokenMapping[accessToken.getCode()] = accessToken
reservationMapping.remove(reservationId)
return accessToken.getCode()"This approach adds a new state (RESERVED) and requires tracking reservations separately from access tokens. We'd also need timeout logic—if the driver reserves but never confirms within 2-3 minutes, the system should auto-cancel and free up the compartment.The tradeoff is added complexity. For the interview scope, the single-phase approach is cleaner and good enough. But in production where you need to guarantee physical package presence, two-phase commit with sensors or manual confirmation would be essential."
What is Expected at Each Level?
Junior
Mid-level
Senior
Mark as read
Track your interview readiness
Your personal checklist helps you know what to study and keep track of your progress.
View ChecklistOn This Page
Understanding the Problem
Requirements
Clarifying Questions
Final Requirements
Core Entities and Relationships
Class Design
Locker
AccessToken
Compartment
Final Class Design
Implementation
Locker
AccessToken
Compartment
Verification
Complete Code Implementation
Extensibility
1. "How would you handle size fallback?"
2. "How would you handle compartments that are broken or under maintenance?"
3. "How would you ensure packages are actually deposited before generating access tokens?"
What is Expected at Each Level?
Junior
Mid-level
Senior

Schedule a mock interview
Meet with a FAANG senior+ engineer or manager and learn exactly what it takes to get the job.
Your account is free and you can post anonymously if you choose.