Thread-Safe Stock Inventory: Buy and Sell Without Overselling
Company: Optiver
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of concurrent programming, synchronization primitives, atomicity, and correctness of shared mutable state by requiring implementation of a thread-safe inventory that prevents overselling and preserves updates.
Constraints
- 0 <= initial <= 10^9
- 1 <= quantity <= 10^9 for every buy and sell call
- Up to 10^5 total operations, from up to 64 concurrent threads
- Each operation is amortized O(1) outside of contention for the lock
- operations[i] is one of "StockInventory", "buy", "sell"; the first operation is always the constructor
Examples
Input: (['StockInventory', 'sell', 'sell', 'buy', 'sell', 'sell'], [[100], [30], [80], [50], [120], [1]])
Expected Output: [None, True, False, 120, True, False]
Explanation: The spec example. sell(30) succeeds (70 left); sell(80) fails and leaves 70; buy(50) returns 120; sell(120) drains to 0; sell(1) fails.
Input: (['StockInventory', 'sell', 'buy', 'sell'], [[0], [1], [5], [5]])
Expected Output: [None, False, 5, True]
Explanation: Starting empty, sell(1) fails; buy(5) returns 5; sell(5) exactly drains the inventory and succeeds.
Input: (['StockInventory'], [[10]])
Expected Output: [None]
Explanation: Construct-only edge case: the constructor returns nothing (None); no buy/sell issued.
Input: (['StockInventory', 'buy', 'sell', 'sell'], [[1000000000], [1000000000], [2000000000], [1]])
Expected Output: [None, 2000000000, True, False]
Explanation: Boundary values: initial 10^9, buy 10^9 -> 2*10^9; sell(2*10^9) exactly clears it; the final sell(1) fails.
Input: (['StockInventory', 'sell', 'sell', 'sell'], [[5], [5], [1], [5]])
Expected Output: [None, True, False, False]
Explanation: First sell(5) drains all 5 shares; the remaining sells (1 and 5) both fail because inventory is 0 — demonstrating the no-overselling invariant.
Hints
- A single integer counter plus one mutex is enough: guard both buy and sell with the same lock so no other operation can interleave between the availability check and the update.
- The sell must be all-or-nothing: check `available >= quantity` and do the subtraction inside the same critical section. Never release the lock between the check and the deduction, or two concurrent sells could both pass the check and oversell.
- buy simply adds to the counter and returns the new value under the lock. Keep the critical section to just the read-modify-write so throughput stays high.
- An alternative to a mutex is a lock-free compare-and-swap loop on an atomic counter (retry until the CAS succeeds), which also enforces atomic check-then-act.