A concurrent checkout packet should reject any patch that guards stock with a process-local lock. Two separate worker processes can read one remaining unit and both persist a paid order. The packet hides that second worker behind a deliberately slow payment handler and a delayed commit. A conditional update on the stock row, not a local mutex, is the behavior the rubric rewards.
Why a single-process test lies
An in-memory lock looks correct when the whole suite shares one interpreter and one event loop. The hidden probe starts a second process against the same database and the same sku. Both processes read a remaining quantity of one unit before either process writes its order. Only a predicate on the update statement can make one of those two writes fail.
Hiring loops often stop at the happy path because a single green test feels like coverage. That habit is especially expensive when a coding agent rewrites the handler and the reviewer never leaves one process. The packet in this article treats the second process as the real exam, not as an optional stretch goal. A candidate who cannot explain the lost update should not pass on naming or style alone.
Assignment prompt
The prompt should stay short so the candidate spends time on the race, not on product flavor. It asks for a function that reserves stock and records an order inside one database transaction. It forbids solutions that only work inside a single process or inside a single test thread. It also asks for a test that still fails if that process-local constraint is later removed.
The wording below is only a proposed prompt, not a transcript taken from a live interview loop. Interviewers can paste it into a private gist or a short-lived repository with the fixture already committed. The candidate, whether a human or an agent, should change only the reservation function and its tests. Hidden probe files should stay read-only so a later cleanup pass cannot delete the planted race.
Implement reserve_and_order(conn, sku, qty, request_id).
Decrement on_hand only when the row still has enough units.
Insert exactly one order row for request_id when the decrement succeeds.
Roll back both writes when either write fails.
Do not rely on a lock that lives only inside this process.
Include a test that fails if two callers can oversell one unit.
Fixture the candidate does not edit
The fixture inserts one sku with a single unit and a version column set to zero. The supplied helper pauses the first worker after its select and before its stock write. That pause lasts long enough for the second process to observe the same unchanged stock snapshot. Candidates who delete the pause to quiet the suite should lose credit on the rubric.
The order table carries a unique constraint on request_id, which catches a naive retry that inserts twice. That constraint does not catch two different request identifiers selling the same last stock unit. Interviewers should say that distinction out loud during the readout, because agents often treat uniqueness of the request as uniqueness of the stock. The fixture therefore issues two distinct request identifiers so that the stock race stays clearly visible.
Rubric that ignores a lonely green test
Reviewers should score the two-process probe before they score names, comments, or other formatting choices. A single-process happy-path test remains necessary and is still insufficient as a clear final hire signal. Reviewers should use the checks below as a pass-fail table rather than as a loose impression.
Pass-fail table
| Check | Pass condition | Fail condition |
|---|---|---|
| Happy path | One order exists and on_hand is zero | Exception, missing order, or leftover stock |
| Two-process probe | Exactly one order and no negative stock | Two orders, or on_hand below zero |
| Write predicate | Database rejects the stale decrement | Python computes the next quantity and writes it |
| Lock scope | Safety holds across two processes | threading.Lock, a global dict, or a file lock on one host only |
| Transaction shape | Reserve and order commit or roll back together | Order inserted after a failed reserve, or reserve kept after a failed insert |
| Conflict result | Caller receives a conflict, not a fake success | Exception swallowed, then a success value returned |
| Retry behavior | Same request_id does not create a second order | Blind retry inserts another order or double-decrements |
A patch can pass the first row and fail the second row, and that split is the point of the packet. Interviewers should record which specific rubric row failed rather than collapsing the result into one note. Numeric weights are optional here, but a failed two-process row should block a hire signal. Comments on naming style should wait until after the two-process probe has turned fully green.
Reference solution
The following snippet is an unexecuted example for reviewers, not a claim that a particular vendor ran it. It uses a conditional update and then inserts the order only when that update returns a row. The version column advances so a later writer can detect a stale read of other fields. The unique key on request_id remains only a replay backstop, not the actual stock guard.
def reserve_and_order(conn, sku, qty, request_id):
if qty <= 0:
raise ValueError('qty must be positive')
with conn:
row = conn.execute(
'''
UPDATE inventory
SET on_hand = on_hand - ?,
version = version + 1
WHERE sku = ?
AND on_hand >= ?
RETURNING on_hand, version
''',
(qty, sku, qty),
).fetchone()
if row is None:
raise StockConflict(sku)
conn.execute(
'''
INSERT INTO orders (request_id, sku, qty)
VALUES (?, ?, ?)
''',
(request_id, sku, qty),
)
return 'reserved'
The quantity predicate is the actual fence, because two transactions cannot both satisfy it for the last unit. That returning clause tells the caller whether this particular transaction has actually won the stock write. A missing row means another worker already took the unit, so the order insert must not run. Wrapping both statements in one transaction keeps a failed insert from leaving a silent decrement behind.
Reviewers who prefer an explicit version compare can add a version predicate when the application already loaded that version. That form is stricter when other columns change, and it is unnecessary for a pure decrement of on_hand. The sample omits the compare so candidates are not punished for a correct quantity predicate. Either form fails the rubric if the new quantity is calculated in process memory and written blindly.
Commands for the two-process probe
Run the narrow schema test first so a broken table fails before the race even starts. Then run the probe that forks two workers against one database file or one server database. The example commands below are proposed, and they assume a local checkout with pytest and a small harness. Teams should confirm the printed row counts before they trust a local adaptation of the harness.
python -m pytest -q tests/test_reserve.py::test_single_buyer
python reserve_probe.py --workers 2 --sku SKU-1 --qty 1 --pause-ms 200
What the probe must print
The probe should print one reserved result and one stock conflict, then exit non-zero if the order count is not exactly one. A zero exit code with two inserted orders is a failed packet even when both child processes exit cleanly. Interviewers should keep the database file after the run so the readout can show the rows. Deleting that database file before the readout would hide the only row-level evidence that still matters.
SELECT on_hand, version FROM inventory WHERE sku = 'SKU-1';
SELECT request_id, qty FROM orders WHERE sku = 'SKU-1';
What the queries must show
Expected values are a zero on-hand balance, a version that advanced once, and a single order row. Two separate order rows mean that the quantity predicate was missing or applied much too late. A negative on-hand balance means the writer used a blind assignment from a stale Python integer. Those two failing outcomes should be copied into the written score note without any later softening.
Failure modes worth scoring
Patterns that look finished
- A threading lock makes the single-process test pass, while the second process still sells the last unit.
- The handler reads the balance, subtracts in Python, and writes that integer back without a guarding predicate.
- The order insert runs before the code inspects the update row count, so a conflict still leaves a paid row.
- A broad retry repeats the insert under a new request identifier and decrements stock a second time.
- The conflict exception is swallowed, and the function returns success to a caller that will ship the item.
- A cleanup pass deletes the comment that states why the predicate must live in the update, and the next edit drops the clause.
The last mode is a clarity failure rather than a concurrency failure, and it still predicts the next regression. Agents that equate clean code with fewer lines often remove the sentence that documents the lost update. The rubric can award a separate note for that deletion without letting it outweigh a wrong write. Keep the comment if it states an invariant the test does not spell out in nearby prose.
Where a free model pass fits this packet
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode's free model access can draft several candidate patches from one prompt for the rubric to score. MonkeyCode's free server option can host the database and the two worker processes so the race is not simulated inside one notebook kernel. Those two options are availability claims supplied for this workflow, not measured limits, named models, or a fixed promise. The packet stays usable on any laptop if that hosted option is declined or later unavailable.
What to send and what to withhold
Reviewers should paste the prompt and the fixture, not production connection strings, customer rows, or live payment keys. Generated patches still need the two-process probe, because a fluent explanation of lost updates can wrap a blind write. Treat the model output as an untrusted candidate submission that still needs a readable diff review. A free server is only a convenience for the probe, not evidence about a larger catalog or another isolation level.
Readers who already run a hiring loop can copy the table and the probe without any hosted tool. Readers who want a scratch server plus free model access for those drafts can try MonkeyCode for this narrow workflow. That trial does not replace the rubric, and it should not become the reason a weak patch is accepted. A declined or expired trial changes none of the pass conditions already listed in the table above.
Limits and poor fits
This packet does not prove isolation behavior under a broad anomaly suite, and it does not model a warehouse with many skus. It will mislead teams who need a multi-region inventory design or a formal proof of linearizability. Candidates who were told to use an existing queue lease should not be scored against this checkout prompt. People evaluating a payment capture, a tax engine, or a consensus protocol should write a different packet.
SQLite running in rollback-journal mode can serialize writers so tightly that a sloppy patch appears safe. Reviewers who choose SQLite should enable WAL mode and still run two processes, or point both workers at one server database. A file lock on a single host fails the lock-scope row even when the local probe looks calm. None of these commands were executed for this article, so teams should confirm the row counts themselves.
What to keep from the readout
The hire signal is a conditional write that survives two processes, plus a caller-visible conflict when the predicate misses. Everything else inside the submitted diff should be treated as commentary around that one stored result. A green exit code from the single-buyer test is not a pass, and a tidy function that returns true after an oversell is a fail. Interviewers who keep the score table next to the order query will make that distinction in one readout.
Top comments (0)