Back to Machine Coding
Machine Coding
easy
mid

How would you build a shopping cart UI in React?

Cart state: array of line items {id, name, price, qty}. Operations: add (merge if exists), update qty, remove, clear. Derive totals (subtotal/tax/total) — don't store them. Watch: qty bounds, empty cart state, persistence (localStorage), and that server validates prices.

4 min read·~20 min to think through

A shopping cart is a CRUD list with derived totals — the interview is in the derivations and edge cases.

State

jsx
const [items, setItems] = useState([]); // [{ id, name, price, qty }]

Operations

jsx
const addItem = (product) =>
  setItems((items) => {
    const existing = items.find((i) => i.id === product.id);
    if (existing) {                                  // MERGE, don't duplicate
      return items.map((i) =>
        i.id === product.id ? { ...i, qty: i.qty + 1 } : i);
    }
    return [...items, { ...product, qty: 1 }];
  });

const updateQty = (id, qty) =>
  setItems((items) =>
    qty <= 0
      ? items.filter((i) => i.id !== id)             // qty 0 → remove
      : items.map((i) => (i.id === id ? { ...i, qty } : i)));

const removeItem = (id) => setItems((items) => items.filter((i) => i.id !== id));
const clearCart = () => setItems([]);

Totals — DERIVED, never stored

jsx
const subtotal = useMemo(
  () => items.reduce((sum, i) => sum + i.price * i.qty, 0),
  [items]
);
const tax   = subtotal * TAX_RATE;
const total = subtotal + tax + shipping;

Storing total in state means it can desync from items — compute it. This is the single most important point.

The details being graded

  • Add merges by product id (qty + 1), doesn't add a duplicate line.
  • Quantity bounds — can't go below 1 (or 0 → remove); cap at stock; reject non-integers.
  • Derived totals, memoized — not stored state.
  • Stable id keys for line items.
  • Immutable updates throughout.
  • Empty cart state — a real "your cart is empty" UI, not a blank box.
  • PersistencelocalStorage so the cart survives refresh (it's a guest cache; reconcile with the server on login).
  • Money formattingIntl.NumberFormat, and watch floating-point (work in cents, or round consistently).

The senior point: trust

The cart UI is convenience, not source of truth. Prices, availability, and the final total must be validated/recomputed on the server at checkout — a client-side price is editable. Re-validate cart contents (price changes, out-of-stock) on load and at checkout.

Scaling

useReducer for the operations; Context or a store if the cart is needed app-wide (header badge, etc.); optimistic updates if server-backed.

The framing

"Cart state is an array of line items {id, name, price, qty}. Add merges by product id rather than duplicating; updateQty clamps at 1 (or removes at 0). The key principle: totals — subtotal, tax, total — are derived with useMemo, never stored, or they desync from the items. Then the details: stable id keys, immutable updates, a real empty-cart state, localStorage persistence, and careful money formatting in cents to avoid float errors. And the senior caveat — the cart is a convenience layer; the server must re-validate prices and totals at checkout."

Follow-up questions

  • Why should totals be derived rather than stored?
  • Why must add() merge instead of pushing a duplicate?
  • Why can't the client-computed total be trusted?
  • How do you avoid floating-point errors with money?

Common mistakes

  • Storing total/subtotal in state — desyncs from items.
  • add() pushing a duplicate line instead of incrementing qty.
  • No quantity bounds — negative or zero quantities.
  • Trusting the client-side price/total at checkout.
  • Float math on money instead of cents; no empty-cart state.

Performance considerations

  • Small scale typically. Memoize derived totals so they don't recompute on unrelated renders; memoize line-item rows. The real concern is correctness (derivation, money math), not speed.

Edge cases

  • Adding an item already in the cart.
  • Quantity set to 0 or negative.
  • Item goes out of stock or changes price while in the cart.
  • Empty cart.
  • Very large quantities or many distinct items.

Real-world examples

  • Any e-commerce cart; cart state often in Context/Zustand for the header badge.
  • Guest carts persisted to localStorage, merged with the server cart on login.

Senior engineer discussion

Seniors derive totals instead of storing them, merge on add, clamp quantities, persist as a guest cache, handle money carefully, and stress that the server is the source of truth for prices and totals.

Related questions