Low-Level Design: Shopping Cart System — Persistence, Pricing, and Checkout Coordination

Core Entities

Cart: cart_id, user_id (nullable for guest), session_id (for guest carts), status (ACTIVE, MERGED, ORDERED), created_at, updated_at, expires_at. CartItem: item_id, cart_id, product_id, variant_id (size/color), quantity, unit_price_at_add (snapshot of price when added), added_at. Product: product_id, name, base_price, inventory_count, is_active. AppliedPromo: promo_id, cart_id, code, discount_type, discount_value, applied_at.

Cart Persistence and Guest-to-User Merge

class CartService:
    def get_or_create_cart(self, user_id=None, session_id=None) -> Cart:
        if user_id:
            cart = self.repo.get_active_cart_for_user(user_id)
            if cart: return cart
            # Check for guest cart to merge
            if session_id:
                guest_cart = self.repo.get_cart_by_session(session_id)
                if guest_cart:
                    return self.merge_carts(guest_cart, user_id)
            return self.repo.create_cart(user_id=user_id)
        else:
            cart = self.repo.get_cart_by_session(session_id)
            if cart: return cart
            return self.repo.create_cart(session_id=session_id)

    def merge_carts(self, guest_cart: Cart, user_id: int) -> Cart:
        user_cart = self.repo.get_active_cart_for_user(user_id)
        if not user_cart:
            guest_cart.user_id = user_id
            guest_cart.session_id = None
            return self.repo.save(guest_cart)
        # Merge guest items into user cart
        for item in guest_cart.items:
            existing = self.repo.find_item(user_cart.cart_id, item.product_id, item.variant_id)
            if existing:
                existing.quantity += item.quantity
            else:
                item.cart_id = user_cart.cart_id
            self.repo.save_item(item)
        guest_cart.status = CartStatus.MERGED
        self.repo.save(guest_cart)
        return user_cart

Price Snapshots and Stale Price Detection

CartItem stores unit_price_at_add (the price when the item was added). This ensures the cart total is stable even if the product price changes. At checkout: compare CartItem.unit_price_at_add against the current product price. If the price decreased: honor the lower current price (customer benefit). If the price increased: show a warning (“Price has changed from $X to $Y”) and require acknowledgment. The cart total displayed to the user uses the snapshot price for consistency during the session. Price change detection: a background job can scan active carts for items with stale prices and trigger a notification to the user (“Price alert: item in your cart changed”).

Add to Cart with Inventory Check

Two approaches: (1) Soft availability check: check inventory on add but do not reserve. Fast and simple. Risk: items can sell out before checkout. Show “only N left” warnings. (2) Hard reservation on add: reserve inventory immediately on add (same as ticket booking). Ensures items won’t sell out. Risk: users abandon carts, tying up inventory. Use a cart expiry TTL (e.g., 30 minutes) to auto-release abandoned reservations. Decision: for scarce inventory (concert tickets, limited sneaker drops): hard reservation. For most e-commerce: soft check. Implementation of soft check: SELECT inventory_count FROM products WHERE product_id = :id. If inventory_count >= requested_quantity: add to cart. Else: return InsufficientStockError with the available count.

Cart Total Calculation

Calculate at read time (not stored): avoids stale totals when prices change. subtotal = SUM(item.quantity * item.unit_price_at_add). Apply promos: for percentage discount, discount = subtotal * rate. For fixed amount, discount = min(promo_value, subtotal). For free shipping, set shipping_cost = 0. tax = (subtotal – discount) * tax_rate (varies by shipping address). shipping_cost = compute_shipping(weight, dimensions, destination, carrier). total = subtotal – discount + tax + shipping_cost. The calculation is performed in the cart summary API response. Store the promo discount type and value, not the pre-calculated discount amount (prices may change).

Asked at: Shopify Interview Guide

Asked at: Uber Interview Guide

Asked at: Airbnb Interview Guide

Asked at: Stripe Interview Guide

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

Scroll to Top