Cart Storage Options
There are three main approaches to storing a shopping cart. Client-side storage keeps the cart in the browser’s localStorage as a JSON array of items. No server storage is required, reads are instant, and the cart works without authentication. The downside is that the cart is tied to a single device and browser — clearing localStorage or switching devices loses the cart entirely.
Server-side Redis storage models the cart as a hash: key cart:{user_id} with fields sku_id mapping to quantity. A TTL of 30 days on the hash means carts expire after inactivity without a cleanup job. The cart survives device changes because it is keyed to the user, not the browser. Redis hash operations are O(1) per item and the entire cart can be fetched in a single HGETALL.
Hybrid storage is the most common production approach: anonymous users get a client-side cart (no login required, lower friction), and on login the client-side cart is merged into the server-side cart. The anonymous cart is identified by a session_id stored in a cookie, allowing temporary server-side storage even before the user authenticates.
Cart Data Model
Each cart item contains: sku_id (the specific product variant being purchased), quantity (integer, minimum 1), price_at_add (the price when the item was added to cart, stored for display purposes), and added_at (timestamp for sorting and abandonment tracking).
Product details — name, image URL, description, current price — are not stored in the cart. They are fetched from the catalog service when the cart is loaded for display. This keeps the cart data small and ensures product information is always current. The price_at_add is stored as a courtesy to show the customer "price when added" but is not the authoritative price for checkout. The cart service does not own product or pricing data; it owns only the mapping of user to SKUs and quantities.
Merge on Login
When an anonymous user adds items to their cart and then logs in, the system must reconcile two carts: the anonymous cart (identified by session cookie) and the existing saved cart for the authenticated user.
The merge algorithm iterates over all items in the anonymous cart. For each SKU: if the SKU is not in the saved cart, add it with its quantity from the anonymous cart. If the SKU is already in the saved cart, resolve the conflict according to site policy — common options are taking the maximum quantity (more generous to the user), summing the quantities (can result in unexpectedly large quantities), or prompting the user. After merge, the anonymous cart is deleted and the session cookie is updated to point at the authenticated cart. Orphaned anonymous carts (session expired without login) are cleaned up automatically when their TTL expires in Redis, requiring no explicit garbage collection.
Price Consistency
Prices change. A cart that was assembled three days ago may contain stale prices. The price_at_add field is displayed in the cart UI so the customer can see what price they expected, but it is never used for the actual charge.
At checkout initiation, the cart service calls the pricing service with the full list of SKU IDs and receives current prices. If any price has changed from the stored price_at_add, the UI shows a "price changed" notice next to the affected items before the customer confirms. The order total displayed at confirmation always uses the current price. This prevents a class of attacks where a customer adds an item during a flash sale, the sale ends, and they complete checkout expecting the sale price — the system makes the price change explicit rather than silently charging the new amount. It also prevents exploitation of stale prices in the reverse direction: if a price dropped, the customer is charged the lower current price.
Inventory Reservation
The cart itself does not reserve inventory. Adding an item to a cart is a non-binding expression of intent. Reserving inventory for every cart add would block popular items from other customers during extended browsing sessions. Instead, inventory reservation happens in two stages at checkout.
When the customer initiates checkout (clicks "Proceed to Payment"), the cart service calls the inventory service to place a soft reservation on each SKU: decrement the available quantity by the requested amount and record a reservation with a 10-minute TTL. If a SKU cannot be reserved (insufficient stock), the checkout is blocked and the customer is notified. During the 10-minute window, the items are held for this customer. On successful payment, the inventory service records a hard reservation (permanent deduction). On payment failure, timeout, or cancellation, the soft reservation is released and inventory is restored. This prevents oversell without punishing customers who are just browsing.
Cart Abandonment Recovery
Every cart update records a last_activity_at timestamp. A background job runs periodically and identifies authenticated users whose cart has items and whose last_activity_at is older than a configured threshold (commonly 1-4 hours). For these users, an abandonment recovery email is triggered containing a snapshot of cart contents, product images, prices, and a direct link back to checkout.
Before sending, the job checks that no order has been placed since the cart was last active, preventing emails to customers who already completed purchase. Suppression lists respect user email preferences. High-value carts (total above a threshold) may receive a personalized discount code in the reminder to reduce price sensitivity. The timing and content of recovery emails are A/B tested — sending immediately vs. after a delay, with or without a discount, single email vs. a sequence. Attribution is tracked via UTM parameters on the checkout link.
Performance at Scale
Redis pipeline batches multiple hash operations for a cart update into a single network round-trip: a cart update that changes three item quantities issues one pipeline with three HSET commands rather than three sequential calls. For anonymous carts served to unauthenticated users (a significant fraction of e-commerce traffic), cart reads can be cached at CDN edge — the session_id cookie is used as the cache key and the TTL matches the expected cart update interval.
Price refresh is batched: when loading a cart for display, the cart service collects all SKU IDs in a single list and makes one call to the pricing service with all IDs, receiving prices in bulk. This avoids N pricing service calls for a cart with N items. Product detail enrichment (name, image, description) is similarly batched against the catalog service. For the cart API itself, a common pattern is to return minimal cart data (sku_id + quantity only) in the primary response and load enriched product data lazily on the client, so the cart page renders the structure immediately while product details fill in.
Saved for Later
Saved-for-later is a distinct list from the active cart. When a customer moves an item from active cart to saved-for-later, the cart service removes it from the cart:{user_id} hash and adds it to a cart_saved:{user_id} hash with the same data model. The saved list does not participate in inventory reservation or checkout flow — it is purely a wishlist with a different UI placement.
The saved list is displayed below the active cart in the cart UI with a "Move to Cart" button per item. Moving an item back to the active cart removes it from the saved hash and adds it to the cart hash. Items in the saved list are subject to the same price-at-add display and current-price refresh on load. The saved list has its own TTL, typically longer than the active cart (e.g., 90 days), since customers may return for items they deferred. Saved-for-later data feeds personalization signals: items a customer explicitly deferred are strong purchase intent signals for remarketing.
{ “@context”: “https://schema.org”, “@type”: “FAQPage”, “mainEntity”: [ { “@type”: “Question”, “name”: “What are the tradeoffs between Redis and a database for shopping cart storage?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Redis provides sub-millisecond reads and writes, built-in TTL for session expiry, and horizontal scaling via clustering, making it ideal for active cart state. The downside is that Redis is memory-bound and durability depends on AOF/RDB persistence configuration—data can be lost on failure without careful setup. A relational or document database is more durable and supports complex queries (e.g., cart analytics, recovery), but adds latency and operational overhead. A common pattern is Redis as the primary cart store for active sessions with async write-through to a database for persistence and analytics. Abandoned carts are later queryable from the DB.” } }, { “@type”: “Question”, “name”: “What is the best strategy for merging an anonymous cart when a user logs in?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “On login, retrieve both the anonymous cart (keyed by session/device token) and the authenticated user’s saved cart. Merge by iterating items: if an item exists in both, take the higher quantity or the authenticated cart’s quantity depending on business rules. Items only in the anonymous cart are added to the user’s cart. After merge, persist the merged cart under the user ID and delete the anonymous cart. Handle conflicts (e.g., a product removed from the catalog) by silently dropping invalid items and notifying the user. The merge should be idempotent so repeated login events don’t duplicate quantities.” } }, { “@type”: “Question”, “name”: “How do you ensure price consistency between cart display and checkout in a shopping cart system?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Store the display price in the cart as a cache for UX, but always recompute the authoritative price at checkout from the pricing service using the current SKU, user segment, and active promotions. On checkout initiation, diff the recomputed prices against the cart display prices and surface any changes to the user before payment. Never trust client-submitted prices. For flash sales or time-sensitive pricing, set a cart price lock window (e.g., 15 minutes) after which prices are re-fetched. Log all price deltas between cart creation and checkout for fraud detection and reconciliation.” } }, { “@type”: “Question”, “name”: “How do you handle inventory reservation during checkout to prevent overselling?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Use a two-phase reservation: on checkout initiation, place a soft hold (reservation) on inventory for each item with a short TTL (e.g., 10 minutes). If payment succeeds, convert the hold to a confirmed deduction. If payment fails or TTL expires, release the hold. Holds should be implemented with optimistic locking or atomic compare-and-swap in the inventory store (e.g., Redis SETNX or a database row-level lock). For high-concurrency SKUs, a dedicated inventory service with a request queue per SKU serializes deductions. Monitor hold expiry carefully to avoid ghost reservations that block real purchases.” } }, { “@type”: “Question”, “name”: “How do you design cart abandonment email recovery in a shopping cart system?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Emit a cart_updated event to a message queue on every cart modification for authenticated users. A consumer updates a last_active timestamp and cart snapshot in a recovery store. A scheduled job (e.g., every 30 minutes) queries carts with last_active older than a threshold (e.g., 1 hour) and no completed order, then enqueues an abandonment email task. The email renders from the stored cart snapshot and includes a deep link that restores the cart. Unsubscribe and suppression lists must be checked before sending. Track open and recovery rates per campaign for optimization. Limit sends to at most one per abandonment event to avoid spam.” } } ] }See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Shopify Interview Guide
See also: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering