Engineering Atlas › Low-Level Design

Code an online shopping platform

15 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

Requirements

Functional:

Non-functional:

Out of scope: search ranking, recommendations, reviews, multiple sellers per SKU, shipping address validation, tax and the payment gateway itself. Name them, mention where they would plug in, and move on.

Entities and relationships

Relationships: a customer owns one cart. A cart references products by SKU only. An order is composed of its lines, and a line copies product facts instead of pointing at the live product.

Class diagram

classDiagram
    class Store {
      -carts: Map~UserId, Cart~
      -inventory: Map~Sku, int~
      -orders: Map~OrderId, Order~
      +addToCart(user, sku, qty)
      +placeOrder(user, couponCode) Order
      +pay(orderId)
      +ship(orderId)
      +deliver(orderId, day)
      +cancel(orderId) int
      +returnOrder(orderId, day) int
    }
    class Product {
      +sku: string
      +category: string
      +price: int
    }
    class Cart {
      +items: Map~Sku, int~
    }
    class Promotion {
      <<interface>>
      +discount(line) int
    }
    class Coupon {
      +code: string
      +minSubtotal: int
      +off: int
    }
    class Order {
      +id: string
      +lines: OrderLine[]
      +subtotal: int
      +discount: int
      +total: int
    }
    class OrderLine {
      +sku: string
      +unitPrice: int
      +qty: int
      +discount: int
    }
    Store "1" o-- "many" Product
    Store "1" *-- "many" Cart
    Store "1" o-- "many" Order
    Order "1" *-- "many" OrderLine
    Store --> Promotion
    Store --> Coupon
    PercentOffCategory ..|> Promotion
    BuyXGetY ..|> Promotion

Key flows

Checkout is one critical section: price the cart, check every line's stock, decrement all of it, record the order, clear the cart. Pricing is pure and could run before the lock; the stock check and decrement cannot.

sequenceDiagram
    participant C as Customer
    participant S as Store
    participant P as Promotions
    participant I as Inventory
    participant O as OrderBook
    C->>S: placeOrder(user, "SAVE5")
    S->>S: read cart, copy current prices
    S->>P: discount(line) for each line
    P-->>S: best discount per line
    S->>S: apply coupon if minimum met
    S->>I: lock, check all lines
    I-->>S: enough stock
    S->>I: decrement all lines
    S->>O: record Order O7 (PLACED)
    S->>S: clear cart, unlock
    S-->>C: Order O7, total 1900
    C->>S: pay(O7)
    S->>O: PLACED to PAID

Design patterns used and why

Concurrency and edge cases

In the wild

Down the stack

Layer 1The contract

placeOrder either creates exactly one order whose lines match the cart at current prices and decrements stock for every line, or changes nothing (out of stock, empty cart, unknown coupon). total = subtotal - discount holds on every order, with all values in integer cents, and is recomputable from the lines. Status transitions outside the table fail and change nothing. Cancel and return each restock and refund at most once.

Layer 2The mechanism

In memory, the store is a few hash maps (SKU to product, SKU to on-hand count, user to cart, order id to order) behind one mutex. Checkout is O(L × P) for L lines and P promotions, which for real carts (L under 50, P under 20 applicable) is a few hundred function calls. In a relational database the same model becomes products, inventory(sku PK, on_hand CHECK (on_hand >= 0)), carts(user_id, sku, qty), orders(id, user_id, status, subtotal, discount, total, version) and order_lines(order_id, sku, unit_price, qty, discount). The CHECK constraint is a second line of defence against overselling. Status transitions use optimistic concurrency: UPDATE orders SET status = 'SHIPPED', version = version + 1 WHERE id = $1 AND status = 'PAID' AND version = $2. Amazon's own cart, as described in the Dynamo paper, goes further and accepts writes on any replica, merging divergent carts later; that is the distributed answer, and the Design Amazon system design lesson walks through it.

Layer 3The machine

An uncontended mutex costs tens of nanoseconds; pricing a 10-line cart against 10 promotions is around 100 virtual calls (a few nanoseconds each through a vtable or Go itab) plus small allocations for the order lines, well under 10 microseconds in total. The expensive step is durability: a Postgres commit waits for the WAL fsync, roughly 0.1 to 1 ms on NVMe, and a payment authorisation call takes 200 ms to 2 s over the network. That ratio is why payment authorisation never happens inside the inventory lock: holding a row lock for two seconds on a hot SKU during Prime Day would serialise every buyer of that item behind a remote call. The critical section stays local; payment follows as a separate transition.

Why it's built this way

  1. Prices and stock change while customers browse, so the cart stores intent (SKU, quantity), not facts.
  2. Customers dispute charges, so the order copies every fact that set its price, which makes it an immutable snapshot.
  3. Stock is scarce and contended, so the check and decrement of all lines happen in one atomic step.
  4. Remote calls are a thousand times slower than local work, so payment runs outside the lock as its own state transition.
  5. Merchandising invents new deals every week, so promotions are strategies combined by one named policy, and checkout does not change.

Extensibility

Variant: shopping cart with price snapshots and merge

The main design treats the cart as pure intent, so it is a plain map from SKU to quantity and all the hard work waits for checkout. A cart-centred variant keeps that rule for what the customer pays but makes the cart itself a richer object, because carts live for days and are edited from several places.

Compared with the main design, the order, stock and state machine code are unchanged. What grows is the cart: snapshot field, version column, merge function and a checkout step that returns "prices changed" as a normal outcome rather than an error. Test the merge with the limit and stock cases, and the version check with two interleaved writers.

Leadership lens

Check yourself

  1. Why does the cart store SKUs and quantities but no prices?

    Show answer

    Prices change while items sit in carts, so any price stored there is stale. Pricing at checkout and copying the result into the order gives one moment where price is decided and recorded.

  2. A cart has three lines and the third is out of stock. What must happen to the first two, and how do you guarantee it in a database?

    Show answer

    Nothing must be decremented. Run all conditional decrements in one transaction, locking rows in sorted SKU order, and roll back if any update affects zero rows.

  3. Why keep payment authorisation outside the inventory critical section?

    Show answer

    A remote payment call takes hundreds of milliseconds, so holding stock locks across it would serialise every buyer of a hot SKU. Reserve stock quickly, then authorise as a separate transition with cancel as the compensation.

  4. A merchandiser wants a coupon to stack with a 50% category sale. Where does the code change?

    Show answer

    Only in the combination policy. The promotions and coupon classes stay as they are, which is the payoff of keeping the rule in one named place.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →