Skip to content
System design course
Ch.4 · Designing real systems·how to build it ·7 min read

Building an e-commerce store (Amazon)

Implement atomic inventory decrement that prevents overselling, the cart store, and the checkout saga with reservations and compensations.


Inventory: the atomic decrement (no overselling)

The core correctness operation — only sell if stock remains, atomically:

-- succeeds only if enough stock; returns 0 rows if not → reject the order
UPDATE inventory
   SET available = available - :qty
 WHERE sku = :sku AND available >= :qty
RETURNING available;

Because the DB applies this atomically, concurrent buyers of the last unit can’t both succeed — one update finds available >= qty false. This conditional decrement is the anti-oversell guarantee.

Reserve-and-confirm (better checkout UX)

For a smoother flow, reserve stock during checkout and confirm on payment:

def reserve(sku, qty, order_id):
    ok = db.execute(DECREMENT_SQL, sku=sku, qty=qty)       # move to "reserved"
    if not ok: raise OutOfStock()
    reservations.put(order_id, sku, qty, expires=now()+15*60)   # hold; auto-release

def release_expired():                                     # job scheduler
    for r in reservations.expired(): restock(r.sku, r.qty)  # return abandoned holds

Cart store

Per-user, fast, persistent; re-validated at checkout (never trust cart prices/stock):

def add_to_cart(user, sku, qty):
    cart_kv.update(user, lambda c: {**c, sku: c.get(sku, 0) + qty})   # KV store, per user
def checkout(user):
    cart = cart_kv.get(user)
    items = revalidate(cart)            # current price + availability NOW
    return start_order_saga(user, items)

The checkout saga

Coordinate inventory, payment, and order creation with idempotent steps + compensations:

def start_order_saga(user, items):
    order = orders.create(user, items, state="placed")
    Saga(order.id, [
        Step("reserve_inventory", reserve_all,  release_all),
        Step("authorize_payment", auth_payment, void_auth),
        Step("create_order",      finalize,     cancel_order),
        Step("capture_payment",   capture,      refund),
    ]).run()                            # persisted/resumable; compensate on any failure
    enqueue_fulfillment(order.id)       # async shipping after payment
    return order.id

The order is a durable state machine (placed → paid → shipped → delivered), advanced by events idempotently — a crash resumes, never double-charges.

Catalog, search, recommendations (read path)

  • Product pages denormalized, cached, and CDN’d (mostly static); sharded by product id.
  • Search via an inverted index with facets (Chapter 4 search), updated async from the catalog.
  • Recommendations precomputed (collaborative filtering) and served from a fast store.

Scale and failure handling

  • Browse/search (read-heavy) → cache + CDN + replicas + index; the dominant traffic.
  • Hot SKU at a flash sale → the atomic decrement serializes per-SKU (bottleneck); shard inventory by SKU, use atomic counters, and queue/throttle if needed.
  • Payment fails → saga compensates (release inventory, void auth).
  • Crash mid-checkout → saga resumes from persisted progress (idempotent).
  • Peak events → autoscale stateless services; pre-warm caches; the CP inventory path is the scaling pressure point.

The takeaway

Concrete signals: the conditional atomic decrement (or reserve-and-confirm) that makes overselling impossible, a per-user cart re-validated at checkout, and a durable order saga with compensations. Read-heavy catalog (cache/CDN/index) split from a CP inventory/order path is the e-commerce blueprint — and the payment step deserves its own design, next.