Senior Engineering Interview Handbook / Chapter 61
Complete Practical-Engineering Mock
A timed inventory-reservation exercise that carries one feature through contract, implementation, tests, review pressure, refactoring, and production limits.
Preparing audio…
Audio edition
Complete Practical-Engineering Mock
Page tools
Carry one feature to the finish
A practical-engineering round compresses an ordinary piece of product work until every shortcut becomes visible. You have to turn an incomplete request into a contract, change state without breaking an invariant, select a few tests, respond to review, and stop with working software. The feature is small; the chain of judgment is not.
This mock gives you 75 minutes. Use a familiar language and its simplest test runner. Start from an empty file or a tiny project. Do not add a web framework or database: those would let setup consume the part of the exercise that matters.
Here is the request:
A small commerce service holds stock for products. An order must reserve stock before payment. Implement inventory reservation without overselling. Repeated reserve requests must be safe. Expose a small API, write focused tests, and be prepared to refactor the design after review.
Assume one process and in-memory storage. Begin with one SKU per reserve call. Support reservation and release; payment commit can remain a named lifecycle state. You may discuss concurrency, persistence, expiration, and multi-SKU orders after the in-memory slice works.
The version marker in the map belongs to the later concurrency review. The first single-threaded pass does not need it.
Before reading further, set a timer and spend twelve minutes on three things:
- Write the operations you intend to support.
- Write the invariant that must survive every accepted command.
- Choose the identity of a duplicate request and say what a caller receives.
Then explain your proposed first test aloud. If you cannot explain why it comes first, you are choosing tests by file order rather than risk.
The first checkpoint
A coherent opening does not need an architecture speech. It needs a boundary the interviewer can examine. One reasonable contract is:
add_item(sku, total_quantity)
reserve(order_id, sku, quantity) -> ReserveResult
release(reservation_id) -> ReleaseResult
available(sku) -> int
The central invariant is that active reservations never make available stock negative. Idempotency adds a second obligation: retrying the same command must not reserve the same stock twice.
For this exercise, use (order_id, sku) as the command key. That choice needs
one qualification. A retry with the same key and the same quantity is a
duplicate; the same key with a different quantity is a conflict, not a retry.
Silently returning the earlier reservation would make the caller believe its
new request succeeded. In a real API, an explicit idempotency key may be a
better contract, especially if an order can make several legitimate holds for
one SKU.
Say the finish line before coding:
“I’ll implement reserve, release, and available stock. Accepted active reservations cannot push availability below zero. The order and SKU identify a retry, but its quantity must match. I’ll prove success, insufficient stock, duplicate retry, conflicting retry, release, invalid quantity, and unknown SKU. The whole reserve command is one critical section if concurrency enters scope.”
That is enough design to start. It exposes the important assumptions without pretending the in-memory map is already a warehouse system.
Make the state tell the truth
Use a small model:
InventoryItem
sku
total_quantity
reserved_quantity
Reservation
id
order_id
sku
quantity
status: active | released | committed
ReserveResult
status: accepted | duplicate | conflict | rejected | invalid
reservation_id
available_after
reason
Derive availability:
available = total_quantity - reserved_quantity
Storing both available_quantity and reserved_quantity as independently
mutable fields would give reserve and release two values to keep synchronized.
Here, total stock and one reservation count are enough. The reservation record
preserves identity and lifecycle; releasing it changes status rather than
erasing history.
A bare boolean is too weak for the public result. The caller must be able to distinguish an accepted command from a safe duplicate, an altered retry, bad input, unknown inventory, and insufficient stock without parsing a message. The exact result type can follow the language, but those outcomes belong in the contract.
Now implement the thinnest complete path:
reserve(order_id, sku, quantity):
if quantity <= 0:
return invalid("quantity must be positive")
item = items.get(sku)
if item is missing:
return rejected("unknown sku")
existing = reservations_by_order_sku.get((order_id, sku))
if existing is active:
if existing.quantity == quantity:
return duplicate(existing)
return conflict(existing, "retry payload differs")
if item.total_quantity - item.reserved_quantity < quantity:
return rejected("insufficient stock")
reservation = Reservation(new_id(), order_id, sku, quantity, active)
reservations_by_id[reservation.id] = reservation
reservations_by_order_sku[(order_id, sku)] = reservation
item.reserved_quantity += quantity
return accepted(reservation, available(item))
Validation precedes mutation. The duplicate decision precedes the stock check because a successful command may be retried after other orders have consumed the remaining stock. An identical retry should still return its original reservation.
Release is a state transition with one stock effect:
release(reservation_id):
reservation = reservations_by_id.get(reservation_id)
if reservation is missing:
return rejected("unknown reservation")
if reservation.status is not active:
return rejected("reservation is not active")
reservation.status = released
items[reservation.sku].reserved_quantity -= reservation.quantity
return released(reservation, available(items[reservation.sku]))
After release, a fresh reserve using the same order and SKU may create a new active reservation if stock remains. State that policy; do not let it emerge accidentally from map behavior.
Prove the risky behavior
Write the happy path first only long enough to establish that the slice can move. The more valuable tests are the ones that prove rejection paths leave state unchanged.
At minimum, demonstrate these behaviors:
- reserving three of five units succeeds and leaves two available;
- asking for more than the available stock is rejected without mutation;
- an identical retry returns the first reservation ID and leaves availability unchanged;
- the same command key with a different quantity reports a conflict and leaves availability unchanged;
- release restores availability, retains the reservation, and cannot be applied twice;
- zero, negative, and unknown-SKU requests fail before mutation.
A tiny harness is acceptable when no test framework is ready:
add_item("book", 5)
first = reserve("order-1", "book", 3)
retry = reserve("order-1", "book", 3)
altered = reserve("order-1", "book", 4)
assert first.status == accepted
assert retry.status == duplicate
assert retry.reservation_id == first.reservation_id
assert altered.status == conflict
assert available("book") == 2
Run the tests before explaining production. If the basic state transitions do not work, a discussion of transactions is camouflage.
At minute 35, stop adding surface area. Show the interviewer what passes, name anything incomplete, and invite review of the smallest working slice.
Review changes the problem
Use the next three comments as interviewer interruptions. Read one, respond in the code and tests, and only then reveal the next.
“A duplicate request arrives with a different quantity”
If your original implementation returned any existing reservation as a duplicate, add the altered-payload test before changing it. The important distinction is between delivery retry and changed intent. An idempotency key can replay the result of the former; it must not quietly absorb the latter.
Do not broaden the exercise into quantity updates unless asked. A conflict is a complete, honest contract for the current slice.
“Two customers reserve four units from the same stock of five”
Point to the exact race. Both calls can read available == 5, both can pass
the check, and both can increment the count. The invariant is correct on paper
and broken in execution because the check and write are separate.
In this one-process version, a per-SKU guard can cover the active-duplicate lookup, availability check, reservation insert, and counter increment. It must also coordinate release. Do not hold that guard while calling payment, publishing an event, or sending a notification.
In persisted storage, the equivalent boundary might be a transaction with a
row lock, a conditional update such as “increment only if enough remains,” or
optimistic versioning with retry. A repository made from separate get and
save calls does not solve the race; it can merely hide it behind an
interface.
“The service is doing too much. Refactor it”
Keep the tests fixed. Extract only the boundary justified by the criticism. A pure decision function can classify invalid, duplicate, conflict, and insufficient-stock outcomes, while the command handler retains ownership of the atomic state change:
classify(item, existing, quantity)
-> invalid | duplicate | conflict | insufficient | accept
within sku_guard(sku):
item = load_item(sku)
existing = load_active_reservation(order_id, sku)
decision = classify(item, existing, quantity)
if decision is accept:
create_reservation_and_increment_reserved(...)
return result_for(decision)
This extraction separates policy from mutation without scattering the
critical section. If persistence becomes real, prefer a storage operation
that expresses the atomic command—such as try_reserve—over a generic
repository whose reads and writes can be interleaved.
Run the same tests after the move. A refactor is complete when behavior remains proved and the next change has a clearer home, not when the code contains more named layers.
Follow the invariant into production
Once the in-memory slice works, production questions should press on the same contract rather than replace it with a tour of infrastructure.
For multi-SKU orders, sequential independent calls create a partial order when the last SKU fails. Choose all-or-nothing semantics, deterministic lock ordering, or an explicit reservation group with compensation. If partial reservation is allowed, make that a product decision visible in the result.
For expiration, choose who owns time. Lazy expiry on access is simple but can leave abandoned holds visible between reads. A scheduled expiry worker needs safe retry and coordination with release or commit. In either design, inject a clock so the exact boundary can be tested.
For process restart, in-memory reservations disappear. Persistence must make the reservation and stock update atomic, restore active holds, and define what happens to commands whose client timed out before learning the result.
For external effects, change local state first and publish from durable state, often through an outbox or equivalent relay. Calling payment inside an inventory lock makes latency and failure part of the critical section.
These are not missing features in the mock. They are the limits of what its tests prove. A strong finish separates those two categories cleanly.
Debrief from evidence
Inspect the artifact before judging the performance. Ask:
- Can a reader of the API distinguish accepted, duplicate, conflicting, invalid, and rejected commands?
- Does every rejection path prove that stock and reservations were unchanged?
- Can you point to one line or boundary that must become atomic under concurrency?
- Did the review comments change tests before they changed abstractions?
- Did the refactor preserve the public result and the passing suite?
- Can you name exactly what remains unsafe outside one process?
A weak answer identifies a feeling: “I ran out of time,” “the design was messy,” or “I think it went well.” A useful answer identifies evidence and the next rehearsal: “The altered retry exposed an ambiguous contract, and my repository split the check from the write. I need to repeat the concurrency review with an atomic storage operation.”
End the mock with a short handoff:
“Reserve and release work in memory. The tests cover accepted stock, insufficient stock, identical and altered retries, invalid input, unknown inventory, and release. The read-check-write still needs one atomic boundary in a concurrent or persisted service. I have not implemented expiry, multi-SKU atomicity, restart recovery, or external events.”
That statement gives the interviewer working behavior, proof, and limits without claiming more than the exercise established.
Run it again with a harder identity
Do not repeat the same solution immediately. Change one assumption:
An order can reserve several SKUs atomically, and the client supplies an explicit idempotency key. If any SKU lacks stock, none of the reservations may remain active.
Before coding, decide whether the idempotency record stores a whole command result, how concurrent commands acquire SKU ownership without deadlock, and what a timed-out caller receives on retry. The first mock taught a thin slice; this variation reveals whether you understood the invariant or merely copied the shape.
The chapter ends where practical coding meets runtime reality. Correct state transitions in one thread are only the first proof. Processes, threads, memory, scheduling, and finite resource limits decide whether the same contract survives once the program is actually under load.
Continue reading
Full table of contents