100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace

How to Design a Shopping Cart Service

Learn how to design a scalable shopping cart service: fast storage, idempotent writes, guest-cart merging, and checkout revalidation.

mediumQ75 of 224 in System Design Est. time: 5 minsLast updated:
Open Code Lab

Expected Interview Answer

A shopping cart service stores per-user line items in a fast, durable key-value store keyed by cart ID, merges anonymous and logged-in carts on login, revalidates price and stock at checkout rather than trusting cached cart data, and expires idle carts on a TTL.

The cart itself is a small, frequently mutated document (product ID, quantity, chosen variant) so it belongs in something like Redis or DynamoDB rather than a relational table with heavy joins, and it is looked up by a cart ID stored in a cookie or session token for guests and by user ID once authenticated. Writes are idempotent add/update/remove operations so double-clicks or retried requests never duplicate a line item, and every read of cart contents at checkout time re-fetches current price and inventory from the catalog and inventory services instead of trusting whatever price was cached when the item was added. Guest carts merge into the account cart on login using a last-write-wins or quantity-sum strategy, and abandoned carts are expired via TTL after a period of inactivity, often triggering an abandoned-cart email before deletion. The service should be independently scalable from checkout and inventory since cart reads/writes vastly outnumber actual purchases.

  • Keeps cart mutations fast and cheap by using a low-latency key-value store
  • Idempotent operations prevent duplicate line items from retries or double-clicks
  • Revalidating price/stock at checkout avoids selling at stale prices or out-of-stock items
  • TTL-based expiry keeps storage bounded and enables abandoned-cart recovery flows

AI Mentor Explanation

A shopping cart service is like a scorer’s running tally sheet kept beside the pitch, updated ball by ball as runs and wickets accrue, rather than the official match record being rewritten from scratch each time. Anyone can glance at the tally sheet mid-innings the way a shopper reviews their cart, but the umpire only certifies the final score against the actual conditions at the end, just as checkout re-validates price and stock instead of trusting the running tally. If two scorers both note the same boundary, the sheet must not double-count it, mirroring idempotent cart writes. An unused, forgotten tally from a rained-off match is eventually discarded, the way idle carts expire on a TTL.

Step-by-Step Explanation

  1. Step 1

    Model the cart as a lightweight document

    Store cart ID, line items (product ID, quantity, variant) in a fast key-value store like Redis or DynamoDB, not deeply relational tables.

  2. Step 2

    Support guest and account carts

    Issue a cart ID via cookie for anonymous users; merge into the account cart on login using a sum-or-overwrite strategy.

  3. Step 3

    Make writes idempotent

    Add/update/remove operations use idempotency keys or upserts so retries and double-clicks never duplicate line items.

  4. Step 4

    Revalidate at checkout

    Re-fetch current price and inventory from the catalog/inventory services at checkout time instead of trusting cached cart data, and expire idle carts via TTL.

What Interviewer Expects

  • Chooses a low-latency store appropriate for small, frequently mutated documents
  • Explains guest-to-account cart merging without duplication
  • Insists on price/stock revalidation at checkout rather than trusting the cart
  • Mentions idempotency and TTL-based expiry for abandoned carts

Common Mistakes

  • Trusting the price stored in the cart at checkout instead of revalidating
  • Not handling the guest-cart-to-account-cart merge, causing lost or duplicated items
  • Modeling the cart as a heavy relational schema with unnecessary joins
  • Forgetting idempotency, so retried add-to-cart requests duplicate line items

Best Answer (HR Friendly)

A shopping cart service keeps a fast, simple record of what a shopper wants to buy before they check out. It needs to update quickly as items are added or removed, merge a guest’s cart with their account once they log in, and always double-check the real price and stock right before payment so nothing is sold at a stale price.

Code Example

Cart operations with idempotent add and checkout revalidation
async function addToCart(cartId, item, idempotencyKey) {
  const seen = await redis.set(`idem:${cartId}:${idempotencyKey}`, 1, { NX: true, EX: 300 })
  if (!seen) return getCart(cartId) // duplicate request, no-op

  await redis.hincrby(`cart:${cartId}:items`, item.productId, item.quantity)
  await redis.expire(`cart:${cartId}:items`, 60 * 60 * 24 * 14) // 14-day TTL
  return getCart(cartId)
}

async function mergeGuestCartIntoAccount(guestCartId, accountId) {
  const guestItems = await redis.hgetall(`cart:${guestCartId}:items`)
  for (const [productId, qty] of Object.entries(guestItems)) {
    await redis.hincrby(`cart:${accountId}:items`, productId, Number(qty))
  }
  await redis.del(`cart:${guestCartId}:items`)
}

async function checkout(cartId) {
  const items = await redis.hgetall(`cart:${cartId}:items`)
  const validated = []
  for (const [productId, qty] of Object.entries(items)) {
    const { price, stock } = await catalogService.getCurrent(productId)
    if (stock < Number(qty)) throw new Error(`Out of stock: ${productId}`)
    validated.push({ productId, qty: Number(qty), price }) // real price, not cached
  }
  return placeOrder(validated)
}

Follow-up Questions

  • How would you handle two devices editing the same cart concurrently?
  • What happens if inventory drops between checkout revalidation and payment confirmation?
  • How would you scale the cart service independently from checkout during a flash sale?
  • Should the cart service be strongly or eventually consistent, and why?

MCQ Practice

1. Why should checkout re-fetch price and stock instead of trusting the cart’s cached values?

Cart data can go stale between when an item is added and when checkout happens, so checkout must revalidate against the current source of truth.

2. What data store characteristic matters most for a cart service?

Carts are small documents mutated very frequently, so a fast key-value store is a better fit than a heavily relational schema.

3. What problem does an idempotency key on add-to-cart requests solve?

Idempotency keys let the service detect and ignore a repeated request instead of adding the item twice.

Flash Cards

Why store carts in a key-value store instead of relational tables?Carts are small, frequently mutated documents needing low-latency reads/writes, not complex joins.

Why revalidate at checkout?Cached cart price/stock can go stale; checkout must confirm current values before charging.

How are guest and account carts reconciled?On login, the guest cart is merged into the account cart, typically summing quantities without duplicating items.

Why give carts a TTL?To bound storage growth and trigger abandoned-cart recovery flows for idle carts.

1 / 4

Continue Learning