Implement a cart system with real-time price updates
Model the cart as line items with quantities; compute totals as derived state. Apply optimistic UI on user actions, reconcile prices/availability/promotions with the server (the source of truth for money), and push live changes via WebSocket/polling. Handle conflicts gracefully.
A cart looks simple but has a sharp edge: prices are money, and money must come from the server. The client renders and predicts; the server is authoritative.
Data model
cart = {
items: [{ id, productId, qty, unitPrice, name }],
// derived, never stored:
subtotal, discount, tax, shipping, total
}Totals are derived state — compute them, don't store them, or they drift.
Local interactions: optimistic UI
Add / remove / change quantity should feel instant:
- Update local state immediately.
- Fire the API call in the background.
- On success, reconcile with the server response.
- On failure, roll back and show a clear message.
useReducer (or a store) is a clean fit — actions like ADD_ITEM, SET_QTY, SERVER_RECONCILE.
Why the server must own pricing
The client can't be trusted with money — never let the displayed total drive checkout. The server recomputes on every cart mutation and at checkout, accounting for:
- Price changes (catalog updated since the item was added).
- Stock / availability changes.
- Promotions, coupons, tiered discounts, tax, shipping.
- Currency and region.
The client shows a prediction; the server returns the truth; the UI reconciles and flags differences ("Price updated from $20 to $18").
Real-time updates
Prices/stock can change while the cart is open:
- WebSocket or SSE subscription to the products in the cart, or poll on a sensible interval / on tab focus.
- On a change, update the line item and surface it — don't silently change the total. "This item's price changed" with the old and new value.
- Re-validate everything at checkout regardless — real-time updates are UX, the checkout re-check is correctness.
Persistence
- Logged-in: persist server-side so the cart follows the user across devices.
- Guest: localStorage, then merge into the server cart on login.
- Cross-tab:
storageevent or the store's persistence layer to keep tabs consistent.
Conflict handling
- Item went out of stock → mark it, remove from total, tell the user.
- Quantity exceeds stock → clamp and notify.
- Coupon expired → drop it, recompute, explain.
- Always fail loud and clear — silent money changes destroy trust.
Edge: race conditions
Rapid quantity clicks → debounce the API calls or cancel in-flight requests; reconcile to the last server response, not whichever returns last.
Follow-up questions
- •Why can't the client be trusted to compute the final price?
- •How do you merge a guest cart into a user's cart on login?
- •How do you handle rapid quantity changes without race conditions?
- •Optimistic update rollback — how do you implement it cleanly?
Common mistakes
- •Storing computed totals instead of deriving them — they drift out of sync.
- •Trusting client-side prices at checkout.
- •Silently changing the total when a price updates instead of surfacing it.
- •Not handling optimistic-update failure, leaving the UI in a wrong state.
Performance considerations
- •Debounce quantity updates to cut API chatter. Derive totals with memoized selectors. For real-time price feeds, subscribe only to in-cart products, not the whole catalog. Reconcile to the latest server state, not the last-arriving response.
Edge cases
- •Item goes out of stock while in the cart.
- •Price changes between add-to-cart and checkout.
- •Guest cart merge conflicts with an existing user cart.
- •Rapid quantity clicks causing out-of-order API responses.
Real-world examples
- •E-commerce checkout where the server re-prices the cart on every mutation and at the final step.
- •Marketplace carts showing 'price dropped' / 'only 2 left' live banners.