Locker Allocation System

Parcel Delivery Lockers

Design the software behind a bank of parcel lockers: place each package in the smallest free locker that fits, hand the recipient a one-time code, and take the locker back when the package is collected or left too long.

Overview

A locker bank stands in a store or an apartment lobby. A courier arrives with a package, the system opens a locker that is big enough, and the recipient later opens the same locker with a code. Your job is the software in the middle: choosing the locker, generating the code, checking it on pickup, and freeing lockers that nobody collects from.

The problem looks small but touches the parts of design that interviewers care about. There is a resource pool with sizes, so an allocation rule matters. There is a lifecycle per locker, so state matters. Many couriers can arrive at once, so the pool needs a lock. And the code is a secret, so it has to be generated and compared like one.

A strong answer names the allocation rule in the first minute (best fit: the smallest free locker that fits), draws the locker states, and writes the pool with one lock around every change. It also says what happens when a package is never collected, because that is where most first drafts leak lockers.

Scoping the problem

Ask these before you draw anything. Each answer changes the design.

  • How many sizes are there, and does a big locker take a small package? — Decides whether allocation is a lookup or a search — Three sizes; any locker takes a package of its size or smaller
  • Which locker wins when several fit? — Best fit keeps big lockers free for big packages — Best fit, as a swappable policy
  • How does the recipient open the locker? — A code needs secure generation and a limit on guesses — A six-digit code, five wrong tries allowed
  • How long does a package wait? — Sets when a locker is reclaimed and who clears it — A fixed hold time; a courier takes expired packages back
  • Can several couriers use the bank at the same time? — Two couriers must never get the same locker — Yes; one lock in the bank
  • Do we notify the recipient? — Adds an observer hook; the sending itself is outside the system — A callback list; the app or SMS gateway subscribes

Core entities

The Allocation is the record that ties a package to a locker for a while. It holds the code and the deadline, so the Locker itself stays a dumb resource with a size and a state. Keeping the two apart is what lets the bank drop the allocation on pickup and reuse the locker at once.

The locker lifecycle

The Expired state is the one first drafts skip. If the system set the locker straight back to Empty when the hold time passed, the next courier would open a locker that still has a package in it. So the software marks it expired, keeps the package's record, and refuses pickups until a courier clears it.

Best fit against first fit

Both rules search the free lockers. First fit stops at the first one big enough. Best fit picks the smallest one big enough. On a bank with one small, one medium and one large locker, watch what happens to two small packages and then a large one.

  • 1 — small package — S, M, L — S (first in the list) — S (smallest that fits)
  • 2 — small package — M, L — M — M
  • 3 — large package — L — L — L

Ordered that way, the two rules agree. Change the list order to L, M, S, which is how lockers might be numbered on the wall, and first fit gives the first small package the large locker. The large package in step 3 then finds nothing. Best fit does not depend on the order of the list, which is why it is the default in the code. Both rules are O(n) in the number of lockers; a bank has tens of lockers, so the scan is not worth optimizing.

Access codes are secrets

The code is the only thing standing between a stranger and the package, so treat it as a secret in three places.

  • Generating it — Use secrets.randbelow, not random — Python's random module is documented as unsuitable for security; secrets exists for tokens and codes
  • Comparing it — secrets.compare_digest, not == — A constant-time comparison does not leak how many leading digits matched
  • Guessing it — Count wrong attempts and freeze the locker after a few — Six digits give one million codes; five tries out of a million is a 0.0005 percent chance of guessing right

Thread safety

One threading.Lock in LockerBank wraps every method that reads or changes locker state. Two couriers calling allocate at the same time therefore see the free list one after the other, and the second one gets a different locker. The observer callbacks run after the lock is released: a slow SMS gateway must not hold up the next courier, and a callback that calls back into the bank would otherwise deadlock, because Python's Lock cannot be taken twice by the same thread.

What you will be able to do

  • Scope the problem with six questions and state the assumptions you chose.
  • Draw the entities and the locker lifecycle, including the expired state.
  • Explain best fit against first fit with a three-step example.
  • Generate, compare and rate-limit an access code the way a security reviewer expects.
  • Make the bank safe for concurrent couriers with one lock, and say why callbacks run outside it.

Functional requirements

  • allocate(package) places the package in the smallest free locker that fits and returns the locker and a one-time access code, or nothing when no locker fits
  • pickup(package_id, code) opens the locker only for the right code, then frees the locker
  • A wrong code counts as a failed attempt; after five, the locker refuses every code until a courier clears it
  • expire_overdue() marks lockers whose hold time has passed as expired and refuses pickups from them
  • courier_clears(locker_id) returns an expired locker to empty once the courier has taken the package back
  • Interested parties can subscribe to be told when a package is delivered

Non-functional requirements

  • Two couriers can never be given the same locker: every state change happens under one lock
  • Access codes come from a cryptographic random source and are compared in constant time
  • The allocation rule is a swappable policy object; adding a rule does not change the bank
  • The clock is injected, so expiry can be tested without waiting

Interview questions

  • Why best fit rather than first fit? Is the extra work worth it?
  • A customer orders three items that arrive as three packages. How do you give them one code?
  • Who notices that a package has been sitting for three days: a background job or the next allocation?
  • Two couriers arrive at the same time at two banks in different cities. Does one lock still work?
  • How do you stop someone from guessing a code?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio