Bird
Raised Fist0

Amazon Locker Service

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
🏗️
Amazon Locker Service
Design a scalable Amazon Locker system with package assignment, OTP verification, and expiry management.
mediumLibrary and Catalog SystemsFreq: medium
Prerequisites:OOP FundamentalsJava ConcurrencyDesign PatternsSOLID Principles
⚙️
Functional Requirements
  • Assign packages to available lockers atomically
  • Generate and validate OTPs for package retrieval
  • Expire packages and free lockers after a configurable timeout
  • Support concurrent package assignments and retrievals
  • Notify customers about package availability and expiry
  • Allow querying locker and package status
👤
Use Cases
  • Customer requests a locker for package pickup
  • DeliveryAgent assigns package to an available locker
  • Customer receives OTP to retrieve package
  • Customer opens locker using OTP
  • LockerSystemAdmin monitors locker status and expiry
  • System automatically expires uncollected packages
🧑‍💻
Actors
  • Customer
  • DeliveryAgent
  • LockerSystemAdmin
  • LockerDevice
❓
Clarifying Questions to Ask
  • How many lockers and concurrent delivery agents are expected?
  • Is OTP single-use and time-limited? What is the expiry duration?
  • Should the system support partial package retrieval or multiple packages per locker?
  • What happens if a locker is physically malfunctioning?
  • Is there a pricing or penalty model for expired packages?
  • How to handle simultaneous package assignment requests for the same locker?
Out of Scope
✗ Payment processing
✗ User authentication beyond OTP
✗ Physical locker hardware design
✗ Pricing strategy and billing
⚡
QPS Calculation
write qps500
read qps2000
peak concurrent users1000
🖥️
Server Estimate
app servers10
db servers3
cache servers2
Estimates based on expected daily package volume, OTP generation frequency, and concurrent locker accesses.
🔢
Enumerations
enum LockerStatus
AVAILABLE | OCCUPIED | OUT_OF_SERVICE
Defines locker states to prevent invalid operations and enable exhaustive switch-case handling.
enum PackageStatus
ASSIGNED | COLLECTED | EXPIRED
Tracks package lifecycle states to enforce business rules and expiry logic.
enum OTPStatus
ACTIVE | USED | EXPIRED
Manages OTP validity and prevents reuse or expired OTP acceptance.
📦
Locker
3 fields
private final String lockerIdprivate volatile LockerStatus statusprivate volatile Package assignedPackage
Represents a physical locker with state and assigned package; manages atomic package assignment and release.
public synchronized boolean assignPackage(Package pkg)Atomically assigns a package if locker is AVAILABLE; returns false if occupied or out of service.
public synchronized void releaseLocker()Frees the locker by clearing assigned package and setting status to AVAILABLE.
public LockerStatus getStatus()Returns current locker status; volatile ensures visibility across threads.
📦
Package
5 fields
private final String packageIdprivate volatile PackageStatus statusprivate final String customerIdprivate final long assignedTimestampprivate volatile OTP otp
Represents a package with lifecycle state, assigned customer, and OTP for retrieval.
public synchronized void markCollected()Marks package as COLLECTED atomically to prevent duplicate collection.
public synchronized void markExpired()Marks package as EXPIRED atomically when expiry time passes.
public boolean isExpired(long currentTimeMillis)Checks if package expiry time has passed.
📦
OTP
3 fields
private final String codeprivate volatile OTPStatus statusprivate final long expiryTimestamp
Manages OTP code, status, and expiry for secure package retrieval.
public synchronized boolean validateAndUse(String inputCode, long currentTimeMillis)Validates OTP code and expiry; marks as USED if valid; returns false otherwise.
public boolean isExpired(long currentTimeMillis)Checks if OTP has expired based on timestamp.
📦
LockerManager
2 fields
private final Map<String, Locker> lockersprivate final ScheduledExecutorService expiryScheduler
Manages locker pool, package assignments, OTP generation, and expiry scheduling.
public synchronized boolean assignPackageToLocker(String lockerId, Package pkg)Assigns package to locker atomically; generates OTP and schedules expiry.
public synchronized boolean validateOTPAndOpenLocker(String lockerId, String otpCode)Validates OTP for locker and opens locker if valid.
private void expirePackages()Periodically expires packages past expiry time and frees lockers.
📦
OTPGenerator
0 fields
Generates secure, random OTP codes for package retrieval.
public String generateOTP()Generates a random OTP string.
🔗
Relationships
LockerManager◆──►Locker[1..*]
compositionLockerManager owns lockers; lockers cannot exist independently
Locker◇──►Package[0..1]
aggregationPackage can exist independently but is assigned to Locker temporarily
Package◆──►OTP[1]
compositionPackage owns OTP; OTP lifecycle tied to package
LockerManager──►OTPGenerator[1]
associationLockerManager uses OTPGenerator but does not own it
🗄️
Lockers
PK: lockerId
lockerId STRINGstatus STRINGassignedPackageId STRING
Stores locker details and current assignment status.
🗄️
Packages
PK: packageId
packageId STRINGcustomerId STRINGstatus STRINGassignedTimestamp BIGINTotpCode STRINGotpExpiry BIGINT
Stores package lifecycle and OTP details.
🔌
AssignPackage
Request
{
  "lockerId": "string",
  "packageId": "string",
  "customerId": "string"
}
Response
{
  "success": "boolean",
  "otpCode": "string",
  "message": "string"
}
Assigns a package to a locker and returns OTP for retrieval.
🔌
ValidateOTPAndOpenLocker
Request
{
  "lockerId": "string",
  "otpCode": "string"
}
Response
{
  "success": "boolean",
  "message": "string"
}
Validates OTP and opens locker if valid.
🔌
QueryLockerStatus
Request
{
  "lockerId": "string"
}
Response
{
  "status": "string",
  "assignedPackageId": "string"
}
Returns current status of locker and assigned package.

Practice

(1/5)
1. In designing a Command interface in Java, should it be an abstract class or an interface, and why? Consider that commands may need to store state for undo functionality.
easy
A. Abstract class, because it allows sharing common state and default undo logic among commands
B. Abstract class, because interfaces cannot declare methods
C. Interface, because Java interfaces can have default methods to share code and state
D. Interface, because commands only declare behavior and state should be external

Solution

  1. Step 1: Analyze state requirements for undo

    Commands often need to store state (e.g., previous values) to support undo, which requires fields.
  2. Step 2: Compare interface vs abstract class in Java

    Interfaces cannot hold instance fields, so abstract classes are better to share common state and default undo logic.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Abstract class supports shared state and behavior for undo [check]
Hint: Use abstract class for shared state in commands [check]
Common Mistakes:
2. In designing a TokenBucketRateLimiter class, should the RateLimiter be an abstract class or an interface? Consider that the rate limiter maintains internal token state and exposes tryConsume() behavior.
easy
A. Abstract class, because it can encapsulate shared token state and partial implementation
B. Interface, because it only defines behavior and state is managed externally
C. Interface, because Java interfaces can have private fields for token state
D. Abstract class, because interfaces cannot define any methods

Solution

  1. Step 1: Analyze state and behavior requirements

    RateLimiter needs to maintain internal token count state and provide tryConsume() behavior.
  2. Step 2: Abstract class vs interface for stateful behavior

    Abstract class can hold shared mutable state and provide partial implementation; interfaces cannot hold instance state.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Abstract class supports shared token state and behavior [check]
Hint: Stateful base class -> abstract class, not interface [check]
Common Mistakes:
3. In a multi-threaded parking system using the Decorator pattern, the parkVehicle() method checks if a spot is available and then marks it occupied. Without synchronization, which exact thread interleaving causes double-booking?
medium
A. Thread 1 checks spot available -> true, context switch; Thread 2 checks spot available -> true; both mark spot occupied concurrently.
B. Thread 1 marks spot occupied, Thread 2 waits and then parks safely.
C. Thread 1 parks vehicle, Thread 2 reads spot status after parking completes.
D. Thread 1 and Thread 2 both wait on a lock, preventing double-booking.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 reads spot as available (true) but before marking it occupied, context switches.
  2. Step 2: Trace Thread 2's actions

    Thread 2 also reads spot as available (true) and proceeds to mark it occupied. Both threads think the spot is free and park vehicles, causing double-booking.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Check-then-act without sync allows both threads to park simultaneously [check]
Hint: Check-then-act gap causes double-booking without sync [check]
Common Mistakes:
4. You want to add a WeekendFeeStrategy to calculate parking fees differently on weekends. Given the current design uses a FeeStrategy interface, what is the minimum change to comply with SOLID principles?
medium
A. Modify the existing FeeStrategy implementation to add weekend logic with an if-else check.
B. Add a boolean flag isWeekend to the ParkingLot class and switch strategies internally.
C. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it where needed without modifying existing classes.
D. Combine all fee calculations into a single UniversalFeeStrategy class with multiple conditionals.

Solution

  1. Step 1: Identify the SOLID principle involved

    The Open/Closed Principle (OCP) requires classes to be open for extension but closed for modification.
  2. Step 2: Apply OCP to fee strategy

    Creating a new WeekendFeeStrategy class extends behavior without modifying existing code, adhering to OCP and Dependency Inversion Principle (DIP).
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    New strategy class -> extend without modifying existing code [check]
Hint: OCP: add new class, don't modify existing ones [check]
Common Mistakes:
5. The Elevator System needs to support ElectricCar vehicles requiring ChargingSpot types. What is the minimum change to the current Java design to add this without modifying existing classes?
hard
A. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility.COMPATIBILITY_MAP; no existing classes modified.
B. Modify Vehicle base class to add isElectric flag and add charging logic directly in ElevatorController.
C. Create a new ElectricVehicle interface and modify all existing vehicle classes to implement it.
D. Add a new ChargingSpot class and modify Elevator class to handle charging spots explicitly.

Solution

  1. Step 1: Extend enums and create subclass

    Adding new enum constants for vehicle and spot types allows type-safe extension without changing existing classes.
  2. Step 2: Update compatibility map externally

    Updating SpotCompatibility.COMPATIBILITY_MAP to include ElectricCar and ChargingSpot relations extends behavior without modifying existing class code.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Enum extension plus compatibility map update avoids modifying existing classes [check]
Hint: Extend enums and maps, no existing class changes [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard