Design a Shopping Cart System
OOP design for an e-commerce shopping cart covering price snapshots, coupon application via Strategy pattern, inventory checks, and cart merging on login.
The Problem
Your e-commerce company handles 40,000 active shopping sessions at any given time. The current cart implementation is a thin wrapper around a product ID list that fetches live prices on every page load. Last week a flash sale dropped prices mid-session, and 300 customers saw their cart totals jump back up when the sale ended 10 minutes later. Worse, guest shoppers lose their carts entirely on login because there is no merge logic.
Shopping cart design tests several subtle OOP problems at once: snapshotting prices at add time, applying multiple discount strategies without hardcoded conditionals, merging guest carts with authenticated carts, and reserving inventory atomically at checkout. The design should show clean entity separation, a Strategy-based discount system, and a well-defined checkout flow.
Design the core classes for a shopping cart system that handles adding items with price snapshots, applying multiple discount strategies, merging guest and authenticated carts, and checking out with inventory reservation.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to turn the vague prompt into a concrete specification. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "When a user adds an item to the cart, should we store the price at that moment or look it up live at checkout?"
Interviewer: "Snapshot the price at add time. The user should see a stable total throughout their session."
Good. Price snapshot on add simplifies the cart model and avoids surprising price swings. You will still need a final price reconciliation at checkout.
You: "Do we need to support guest carts? And what happens when a guest logs in?"
Interviewer: "Yes. Guest users get a session-based cart. When they log in, merge the guest cart into their account cart."
That means every cart needs a dual identity: session ID for guests, user ID for authenticated users. You will need merge logic that handles conflicts when both carts have the same product.
You: "What types of discounts should the system support? Can coupons be stacked?"
Interviewer: "Support percentage off, flat amount off, and buy-one-get-one. Coupons cannot be stacked for now, one coupon per cart."
Three discount types with a shared interface. That is a textbook Strategy pattern signal. One-coupon limit keeps validation straightforward.
You: "What should happen if a product goes out of stock while it is sitting in someone's cart?"
Interviewer: "The item stays in the cart but gets flagged as unavailable. The user cannot check out until they remove it or stock returns."
No silent removal. The cart must track availability state per item and block checkout if any item is unavailable.
You: "When exactly does inventory get reserved? On add-to-cart or at checkout?"
Interviewer: "At checkout. Adding to cart does not reserve stock. Only the checkout flow does a final inventory check and reservation."
That simplifies the add flow but means checkout must handle race conditions where two users try to buy the last unit simultaneously.
You: "Should carts expire after some period of inactivity?"
Interviewer: "Guest carts expire after 24 hours. Authenticated carts persist indefinitely."
You will need an expiry timestamp on guest carts and a cleanup mechanism.
You: "Is payment processing in scope?"
Interviewer: "Model it as an interface. The checkout service calls it, but you do not design the payment gateway internals."
You: "Should we send notifications on checkout or cart events?"
Interviewer: "Send order confirmation on successful checkout. Cart abandonment emails are a future extension."
One notification event for now, but designing with Observer keeps the door open for abandonment reminders later.
Perfect. You have clarified scope and ruled out unnecessary complexity.
Final Requirements
Functional Requirements:
- Users can add, update quantity, and remove items from their cart with price snapshots at add time
- The system supports guest carts (session-based) and authenticated carts (user-based) with merge on login
- One coupon per cart, applied via pluggable discount strategies (percentage, flat, buy-one-get-one)
- Items in the cart reflect real-time availability status (in-stock, out-of-stock, limited)
- Checkout performs atomic inventory reservation and blocks if any item is unavailable
- Guest carts expire after 24 hours of inactivity
Non-Functional Requirements:
- Concurrent checkout attempts for the same last-in-stock item must be handled safely (only one succeeds)
- Adding a new discount type requires a new class, not changes to existing code
- Checkout events trigger notifications through an extensible channel (email for now)
Out of Scope:
- Payment gateway internals (use an interface)
- Cart abandonment emails (future extension)
- Product catalog management
- UI rendering
- Persistence / database layer
- Multi-currency support
- Shipping cost calculation
30-Second Design Summary
Cart owns line items and cart lifecycle, but CartItem snapshots the price at add time so catalog changes do not silently rewrite an active order. CheckoutService coordinates coupon validation, discount calculation, atomic inventory reservation, payment, and notification; DiscountStrategy handles calculation variation; and CartMergeService resolves guest-to-user login. The critical invariant is that checkout either reserves every requested item before charging or leaves the cart and inventory unchanged.
5-Minute Walkthrough
- Define the boundary. The core covers guest/authenticated carts, price snapshots, one coupon, availability, checkout reservation, and guest-cart expiry. Catalog management, payment internals, shipping, and persistence are extensions.
- Model snapshots explicitly.
Productis live catalog data;CartItemstores the unit price and quantity captured for this cart.Carttracks identity (session/user), coupon, timestamps, and items. - Mutate the cart. Add/update/remove operations validate quantity and product identity, then change cart state. Coupon validation remains separate from discount math so policy and calculation do not become one conditional block.
- Checkout atomically. Revalidate cart expiry and availability, reserve all inventory items, calculate discounts from snapshots, charge through the payment interface, and publish a checkout event. If a later step fails, release reservations or record a recoverable pending state.
- Merge and extend. On login, merge compatible guest lines using an explicit quantity/price policy. New discount types, inventory backends, abandonment listeners, and pricing variants should fit behind service boundaries.
Example Inputs and Outputs
Scenario 1: Add item with price snapshot
- Input: User adds "Wireless Mouse" (product #42, current price $29.99) with quantity 2
- Expected: CartItem created with
unitPrice: 29.99,quantity: 2,lineTotal: 59.98 - Why: Validates that the price is captured at add time, not looked up dynamically
Scenario 2: Apply a percentage coupon
- Input: Cart has 3 items totaling $149.97. User applies coupon "SAVE20" (20% off)
- Expected: Subtotal remains $149.97, discount becomes $29.99, final total is $119.98
- Why: Validates Strategy-based discount calculation without modifying cart item prices
Scenario 3: Guest cart merge on login
- Input: Guest cart has [Mouse x2, Keyboard x1]. User logs in with existing cart [Mouse x1, Monitor x1]
- Expected: Merged cart has [Mouse x3, Keyboard x1, Monitor x1]. Guest cart is deleted
- Why: Validates that duplicate products combine quantities and unique items transfer cleanly
Scenario 4: Checkout with partial stock failure
- Input: Cart has [Mouse x2, Keyboard x1]. Mouse has only 1 unit in stock
- Expected: Checkout fails with an out-of-stock error for Mouse. No inventory is reserved for any item
- Why: Validates atomic checkout (all-or-nothing reservation)
Try It Yourself
Try it yourself
Before reading the solution, spend 15-20 minutes sketching your own class diagram. Focus on where the price snapshot lives, how discounts get applied without if-else chains, and what happens during the guest-to-authenticated cart merge. Compare your approach with the walkthrough below.
Step 1: Identify Core Entities
Start by asking: what are the main "things" in this problem? Look for nouns in your requirements. You will find products, cart items, the cart itself, discounts, coupons, and the checkout process.
A common mistake is stuffing everything into a single Cart class that knows about products, pricing, discounts, inventory, and notifications. Good design means each class has a single, clear job.
| Entity | Responsibility | Key attributes |
|---|---|---|
| Product | Catalog data for a sellable item. Immutable reference. | id, name, price, category |
| CartItem | A line in the cart. Holds the price snapshot and quantity for one product. | product snapshot, quantity, unitPrice, addedAt |
| Cart | The container. Owns its items, tracks the applied coupon, knows if it is a guest or authenticated cart. | id, userId, sessionId, items, coupon, createdAt, expiresAt |
| DiscountStrategy | Calculates the discount amount for a cart. Each type is a separate implementation. | (interface) |
| Coupon | Validation metadata for a coupon code. Links to a strategy. | code, strategyType, value, expiresAt, minCartTotal |
| CouponValidator | Checks whether a coupon is valid for a given cart (not expired, meets minimum, etc.). | (service) |
| InventoryService | Checks stock levels and reserves inventory atomically. | (service) |
| CheckoutService | Orchestrates the checkout flow: validate cart, reserve inventory, apply discount, charge payment, notify. | (service) |
| CartMergeService | Handles the guest-to-authenticated cart merge on login. | (service) |
Notice we separated CartItem from Product. A CartItem holds a price snapshot taken at add time; the Product entity represents the live catalog data. Merging them would mean the cart always reflects live prices, which violates our requirements.
Interview tip
When listing entities, mention the orchestrator class (CheckoutService) explicitly. It separates the "what to do" (service) from the "what to know" (model). If Cart has a checkout() method, that is a design smell.
Step 2: Define Relationships and Class Design
Class Diagram
Deriving Cart's Interface
The Cart is the central data structure. Everything revolves around it.
Deriving state from requirements:
| Requirement | What Cart must track |
|---|---|
| "Guest carts and authenticated carts" | userId (nullable for guests), sessionId (always present) |
| "Add, update, remove items" | A mutable list of CartItem objects |
| "One coupon per cart" | An optional Coupon reference |
| "Guest carts expire after 24 hours" | expiresAt timestamp |
| "Price snapshot at add time" | Delegated to CartItem (not Cart's job) |
This gives us the state: id, userId, sessionId, items, appliedCoupon, createdAt, expiresAt.
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Add item with snapshot" | addItem(cartItem) |
| "Remove item" | removeItem(productId) |
| "Update quantity" | updateItemQuantity(productId, newQty) |
| "Calculate total" | getSubtotal() |
| "Check if guest" | isGuest() |
| "Apply coupon" | applyCoupon(coupon) |
Deriving CartItem's Interface
CartItem is a value object that captures a point-in-time snapshot of product data.
Deriving state from requirements:
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.