Inventory Management is Not CRUD — It’s a Concurrency Problem

Inventory Management is Not CRUD — It’s a Concurrency Problem
2 users. 1 product. 0 stock left.
Your ecommerce app works perfectly…
Until two users try to buy the last item at the same time.
What happens?
- Both requests read the same stock
- Both proceed
- You oversell
This is a classic race condition — and most systems fail here.
The Problem
At small scale, your logic looks fine:
- Fetch stock
- Check availability
- Deduct stock
- Confirm order
But this assumes only one request at a time.
In reality:
- Multiple users hit your backend simultaneously
- Reads happen before writes complete
- Both users see the same stock
Example Breakdown
Stock = 1
Request A → reads stock = 1
Request B → reads stock = 1
Request A → proceeds
Request B → proceeds
👉 Final state: stock = -1 (oversold)
Why This Happens
Because your system separates:
- Read (check stock)
- Write (update stock)
These operations are not atomic together.
👉 That gap = race condition
The Solution I Use
Instead of one approach, I combine:
- Reservation system (UX layer)
- Atomic update (DB layer)
1. Reservation System (Checkout Phase)
When the user reaches checkout:
- Reserve the item (soft lock)
- Do NOT reduce actual stock yet
- Add expiry (e.g. 10–15 minutes)
Example Reservation Table
| user_id | product_id | quantity | expires_at |
|---|
Why This Matters
Real users:
- delay payments
- abandon checkout
- retry actions
Without reservation:
- stock looks available to everyone
- checkout becomes unreliable
With reservation:
👉 you simulate temporary ownership
Expiry Handling
If payment is not completed:
- reservation expires
- stock is released
- available again
2. Atomic Update (Final Confirmation)
When payment succeeds:
UPDATE products
SET stock = stock - 1
WHERE id = ? AND stock > 0;
Critical Step (Most Developers Miss This)
You MUST check affected rows.
Pseudo logic:
const result = await db.query(`
UPDATE products
SET stock = stock - 1
WHERE id = ? AND stock > 0
`);
if (result.affectedRows === 0) {
throw new Error("Out of stock");
}
Why This Works
- The update is atomic
- Database guarantees consistency
- Either:
- stock updates safely
- or nothing happens
👉 No race condition
3. Why Both Are Required
Only Atomic Update
✔ Prevents overselling
❌ Bad user experience
Problem: User pays → then gets “out of stock”
Only Reservation
✔ Good UX
❌ Not fully safe
Problem: Still vulnerable during final confirmation
Combined Approach
- Reservation → handles user flow
- Atomic update → guarantees correctness
👉 This is how real systems work
Full System Flow
- User clicks “Buy”
- System creates reservation
- Stock appears reserved
- User completes payment
- Atomic update runs
- Order confirmed
Edge Cases to Handle
1. Payment success but no stock
- atomic update fails
- refund user
2. Expired reservations
- cleanup job removes expired entries
3. High concurrency
- ensure DB indexing:
- product_id
- expires_at
Scaling Considerations
As traffic grows:
- Use Redis for reservations
- Use queues for order processing
- Avoid heavy DB locks
But remember:
👉 Infrastructure does NOT fix bad logic
Key Insight
Most systems don’t fail because of traffic.
They fail because of incorrect concurrency handling.
Final Takeaway
If your system involves:
- inventory
- bookings
- tickets
- limited resources
You are NOT building CRUD.
You are building a concurrent system.
Closing Thought
Before scaling, ask:
👉 “What happens if 10 users do this at the same time?”
If you don’t know the answer — your system will break.