Cart Service And Pricing Engine Design
Asked of: Software Engineer
Last updated
What's being tested
These prompts test backend system design for a high-correctness, user-facing service: modeling carts, coordinating state across devices, preventing double checkout, and producing a price the user can trust. Uber cares because cart and pricing sit on the critical path of order conversion; stale menu data, race conditions, or inconsistent discounts directly cause failed checkouts, refunds, and customer support load. Interviewers are probing whether you can separate cart state, menu/catalog state, pricing computation, and checkout orchestration while being explicit about consistency, idempotency, and scalability tradeoffs. For class-design variants, they are also checking whether you can build extensible domain objects and composable pricing rules without hard-coding every promotion or fee.
Core knowledge
-
Domain model boundaries matter. A cart should store user-selected intent:
cart_id,user_id,merchant_id, item IDs, option/customization IDs, quantities, delivery mode, and version. It should not permanently trust mutable fields like menu price, availability, tax, or ETA without revalidation at pricing and checkout time. -
Single-merchant invariant is common for food delivery carts. Enforce it at the cart service layer: adding an item from a different
merchant_ideither rejects, prompts “start new cart,” or archives the existing active cart. This simplifies fulfillment, pricing, promotions, and checkout consistency. -
Cart lifecycle states should be explicit:
ACTIVE,PRICED,CHECKING_OUT,CONVERTED,ABANDONED,EXPIRED. A one-time checkout guarantee usually requires a state transition likeACTIVE -> CHECKING_OUT -> CONVERTED, guarded by a compare-and-set oncart_versionor a database transaction. -
Optimistic concurrency control fits multi-device carts well. Include
versionorupdated_atin every mutation request; applyUPDATE carts SET ... version = version + 1 WHERE cart_id = ? AND version = ?. If zero rows update, return409 Conflictand let the client refetch/merge. -
Idempotency keys are mandatory for checkout and useful for cart mutation retries. For
POST /checkout, accept anIdempotency-Keyscoped touser_idandcart_id; persist the request hash and final response. Retries after network timeouts should return the same order result, not create duplicate orders. -
Storage design should match access patterns.
Rediscan serve low-latency active cart reads with TTLs, whilePostgres,MySQL, or another transactional store remains the source of truth for checkout-critical state. Index by(user_id, state)for “load active cart” and bycart_idfor direct mutations. -
Consistency choices should be deliberate. Cart edits can tolerate read-your-writes within a region and occasional conflict resolution, but checkout requires strong correctness for inventory/availability, final price, payment authorization, and order creation. Use transactions or a saga with compensating actions where cross-service atomicity is impossible.
-
Pricing should be deterministic and explainable. A useful receipt model is:
Store the pricing input snapshot and itemized output so customer support and reconciliation can reproduce what the user saw. -
Pricing engine extensibility usually comes from composable rules. Define interfaces like
PricingRule.apply(PricingContext, PriceBreakdown)for promotions, membership benefits, delivery fees, small-order fees, taxes, and surge. Rules should be ordered and side-effect-free when possible, because discounts may depend on subtotal before or after fees. -
Menu and availability revalidation is a key edge case. Items can become unavailable, options can be removed, merchant hours can change, or prices can update between add-to-cart and checkout. Return structured errors like
ITEM_UNAVAILABLE,PRICE_CHANGED, orMERCHANT_CLOSED, and preserve user intent where possible. -
Scalability is less about cart size and more about request volume and fanout. A single cart usually has fewer than 100 line items, so simple
O(n)recomputation is fine. The hard part is minimizing synchronous calls to menu, promotion, tax, delivery-fee, and membership services on hot paths while keeping prices fresh. -
Caching must respect correctness boundaries. Cache menu item snapshots, promotion eligibility, and computed quotes for short TTLs such as 30–120 seconds, but invalidate or recompute on checkout. Never rely on a cached quote as final unless it is versioned, unexpired, and all dependent inputs still match.
Worked example
For Design cart management lifecycle service, a strong candidate starts by clarifying: is this for Uber Eats-style carts, can a user have multiple active carts, do carts span merchants, and what correctness guarantee is required at checkout? A good initial assumption is: one active cart per user per marketplace, one merchant per cart, multiple devices per user, and checkout must be exactly-once from the user’s perspective. Organize the answer around four pillars: API design (GET /cart, POST /items, PATCH /items/{id}, DELETE /items/{id}, POST /checkout), data model with cart, cart_item, cart_version, and state transitions, concurrency control using optimistic version checks, and checkout orchestration using idempotency plus final validation.
When discussing storage, you can propose Postgres for durable state and Redis for active-cart cache, but emphasize that checkout reads from or verifies against the durable record. For multi-device behavior, describe what happens when device A updates quantity while device B deletes the same item: one write succeeds, the other gets 409 Conflict with the latest cart. For one-time checkout, show the guarded transition: UPDATE carts SET state='CHECKING_OUT' WHERE cart_id=? AND state='ACTIVE' AND version=?; only the winner proceeds. A concrete tradeoff to flag is pessimistic locking versus optimistic concurrency: locks simplify reasoning but hurt latency and availability under mobile retries, while versioning handles the common low-contention case cleanly. Close by saying that, with more time, you would cover region failover, audit history for customer support, and how stale menu or price changes are surfaced to users.
A second angle
For Design cart and pricing engine classes, the same concepts appear, but the focus shifts from distributed service boundaries to object-oriented domain modeling. Instead of starting with storage and APIs, start with classes like Cart, CartItem, Customization, Money, PricingContext, PriceBreakdown, ReceiptLine, and PricingRule. The interviewer wants to see that item customization affects both validity and price: extra cheese, size upgrades, modifiers, and substitution choices should be represented as structured option selections, not free-form strings. The pricing engine should support rules such as PromotionRule, MembershipDiscountRule, DeliveryFeeRule, SurgeFeeRule, and TaxRule without editing a giant if/else block every time a new fee is introduced. The same correctness concerns still apply: deterministic ordering, rounding rules, idempotent recomputation, and an itemized receipt that explains the final total.
Common pitfalls
Pitfall: Treating the cart as just a key-value blob.
A tempting answer is “store the cart JSON in Redis and update it on every add/remove.” That misses indexing, lifecycle, auditability, conflict handling, and checkout correctness. A stronger answer can still use a JSON column or cache, but it defines durable entities, state transitions, versioning, and source-of-truth semantics.
Pitfall: Over-indexing on microservices before correctness.
Some candidates immediately split cart, pricing, promotions, tax, menu, inventory, and checkout into separate services without explaining transaction boundaries. That sounds scalable but can create inconsistent user-visible behavior. Land better by saying which calls are synchronous on checkout, which can be cached during browsing, and how failures degrade: retry, reject checkout, or show a stale-price warning.
Pitfall: Hard-coding pricing logic.
For class design, a common wrong answer is a calculateTotal() method with nested conditionals for surge, coupons, Uber One, taxes, and delivery fees. That becomes untestable and brittle. Prefer composable rules, explicit rule ordering, Money types to avoid floating-point errors, and golden tests for receipts.
Connections
Interviewers may pivot from here into distributed transactions, idempotent API design, cache invalidation, rate limiting, or order/payment workflow design. They may also ask for object-oriented follow-ups around strategy pattern, chain of responsibility, and how to test pricing rules with deterministic fixtures.
Further reading
-
Designing Data-Intensive Applications — strong background on transactions, replication, consistency, and data modeling tradeoffs relevant to cart and checkout systems.
-
Stripe API Idempotent Requests — practical reference for idempotency-key behavior on retry-prone payment and checkout APIs.
-
Martin Fowler, Money — concise explanation of why monetary values deserve a dedicated type instead of floating-point arithmetic.
Featured in interview prep guides
Practice questions
Related concepts
- Unit Economics And Pricing AnalyticsAnalytics & Experimentation
- Pricing, Demand, And Capacity OptimizationAnalytics & Experimentation
- Delivery Driver Payment And Cost SystemsSystem Design
- API Design, Data Modeling, and IndexingSystem Design
- Payment Processing And Ledger SystemsSystem Design
- Crypto Trading And Order Routing SystemsSystem Design