Compile Ready
All low level design problems
Low Level Design/Intermediate/Real-world Systems

Design a Shopping Cart

Cart items, pluggable discount/pricing rules, and a checkout pipeline with tax and coupons.

Intermediate 45m interview 14m read Medium frequency Popularity 80
Strategy Decorator Composite Observer Amazon Flipkart Adobe

Problem Statement

Design a shopping cart for an e-commerce application. Customers can add products, change quantities, apply a coupon or discount policy, and checkout to produce an immutable order with subtotal, discount, tax, and final total.

The important design decision is separation of concerns: the cart owns item selection and quantity state, while CheckoutService owns price calculation. Discounts are pluggable strategies so a new coupon rule is additive rather than another conditional inside the cart.

Business context

Shopping Cart appears frequently in Amazon, Flipkart, Adobe, and marketplace interviews because it looks familiar but quickly exposes design quality. Candidates must model money safely, keep cart mutation independent from pricing, support coupon variation, and create a checkout boundary that can later integrate payments, inventory reservation, tax engines, and order fulfillment.

A strong solution treats the cart as a domain aggregate, not a calculator. Pricing becomes a separate service composed with Strategy, making promotions testable and replaceable without risking item-management logic.

Functional Requirements

  • Represent products with id, name, unit price, and tax category.

  • Add a product to the cart with a positive quantity.

  • Merge repeated additions of the same product by increasing the line-item quantity.

  • Update or remove a product line by product id.

  • Expose a read-only snapshot of current cart line items.

  • Apply a coupon or discount policy to the cart without changing cart item state.

  • Calculate subtotal, discount, tax, and grand total during checkout.

  • Produce an immutable order containing the purchased items and pricing breakdown.

  • Notify interested observers when the cart changes.

Non-Functional Requirements

Pricing correctness

Use precise decimal money arithmetic and round only at clear money boundaries, never with floating-point doubles.

Separation of concerns

Cart manages selection and quantities; CheckoutService computes totals; discount policies live behind DiscountStrategy.

Extensibility

New coupons, tax plans, cart observers, and checkout integrations should be added as classes, not by editing a giant checkout conditional.

Concurrency awareness

Cart mutations and checkout snapshots must be atomic enough that an order is priced from one consistent item view.

Auditability

Checkout should capture subtotal, discount, tax, total, timestamp, and item snapshot so later price changes do not rewrite historical orders.

Requirement Clarification

QCan a product appear multiple times in the cart?

No duplicate lines in the base design. The cart stores one CartItem per product id and increments quantity on repeated add.

QCan multiple coupons stack?

Base design applies one active DiscountStrategy. Stacking can be added by a composite discount strategy or a promotion engine extension.

QDo we reserve inventory during add-to-cart?

No. Cart is customer intent, not inventory reservation. Inventory reservation belongs at checkout or payment authorization time.

QIs tax per product category or a flat rate?

Use a flat percentage tax in the reference implementation to keep focus on LLD seams. Product tax category is retained for future category-aware tax strategies.

QShould checkout clear the cart?

The base implementation returns an Order and leaves clearing as an application decision. Production systems usually clear after payment succeeds, not merely after price calculation.

UML Class Diagram

Rendering diagram…
**Cart** is the composite aggregate of line items. **CheckoutService** depends on **DiscountStrategy** and reads a cart snapshot to calculate prices without owning cart mutation.

Sequence Diagram

Rendering diagram…
Mutation stays inside **Cart**; pricing happens only when **CheckoutService** asks the active strategy for a discount and then applies tax.

Entity Identification

Product

Immutable catalog value used by cart lines. It owns product identity, display name, unit price, and a tax category hook for future tax policies.

idnameunitPricetaxCategory

CartItem

Immutable line item combining one Product with a positive quantity. It can derive its own line subtotal from product price and quantity.

productquantitylineSubtotal

Cart

Aggregate root for customer intent. It owns the product-id to CartItem map, selected discount strategy, and observer list, but does not finalize totals.

itemsdiscountStrategyobservers

DiscountStrategy

Coupon policy seam. Implementations compute discount amount from cart context and subtotal, while checkout clamps the result for safety.

apply(cart, subtotal)description

CartObserver

Notification seam for UI badges, analytics, or persistence hooks whenever the cart changes.

onCartChanged(cart, event)

CheckoutService

Pricing boundary. It takes a consistent cart snapshot, sums line items, applies discount strategy, computes tax, and returns an Order.

taxRatePercentcheckout(cart)

Order

Immutable checkout result containing item snapshot and money breakdown. It is safe to persist or send to payment because it no longer depends on mutable cart state.

iditemssubtotaldiscounttaxtotalcreatedAt

Design Patterns Used

Strategy

DiscountStrategy isolates coupon behavior. Percentage, fixed-amount, no-discount, and future rule-based promotions implement one method while checkout remains unchanged.

Composite

Cart acts as the aggregate composite over many CartItem leaves. Clients treat the cart as one purchasable selection even though totals come from individual lines.

Observer

CartObserver lets UI counters, analytics, and persistence hooks react to item or coupon changes without being coupled to cart mutation methods.

Factory Method

Static factory methods such as Product.of and DiscountStrategy.percentage create validated domain objects and hide concrete coupon classes from callers.

Step-by-Step Design

  1. 1Model products as immutable catalog values

    A product should not change because a customer modifies the cart. Keep identity and unit price immutable, and create products through a validating factory method.

    public static Product of(String id, String name, String unitPrice, String taxCategory) {
        return new Product(id, name, new BigDecimal(unitPrice), taxCategory);
    }
  2. 2Make cart lines immutable and quantity-based

    CartItem represents one product plus quantity. Updating a quantity returns a new line, which avoids accidental mutation leaking through cart snapshots.

    public CartItem withQuantity(int newQuantity) {
        return new CartItem(product, newQuantity);
    }
    
    public BigDecimal lineSubtotal() {
        return product.getUnitPrice().multiply(BigDecimal.valueOf(quantity));
    }
  3. 3Let the cart own item composition, not checkout math

    Cart merges repeated product additions, updates quantities, removes lines, and exposes a safe snapshot. It stores the active discount policy but does not compute final tax or total.

  4. 4Represent coupons as strategies

    Every promotion implements DiscountStrategy. Checkout asks the strategy for a discount amount and then normalizes it so no coupon can produce a negative total.

    public interface DiscountStrategy {
        BigDecimal apply(Cart cart, BigDecimal subtotal);
        String description();
    }
  5. 5Put subtotal, discount, tax, and total in CheckoutService

    The checkout boundary snapshots items, sums line subtotals, invokes the discount strategy, computes tax, and creates the order. This keeps pricing tests focused on one service.

    BigDecimal subtotal = calculateSubtotal(snapshot);
    BigDecimal discount = normalizeDiscount(strategy.apply(cart, subtotal), subtotal);
    BigDecimal tax = taxableAmount.multiply(taxRatePercent)
        .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
  6. 6Return an immutable order snapshot

    An Order stores item copies and the final money breakdown at checkout time. Later product price updates or cart edits must not rewrite the order history.

Complete Java Implementation

Loading…

Explanation of Every Class

Product

Immutable catalog value with validated id, name, unit price, and tax category. Product.of is the factory method used by clients instead of calling a constructor directly.

CartItem

Immutable line item. It combines a Product and quantity, validates positive quantity, and computes the line subtotal with precise decimal arithmetic.

DiscountStrategy

Strategy interface for all coupon policies. Its static factory methods hide concrete discount classes and keep client code small.

NoDiscountStrategy

Null-object style strategy that returns a zero discount. It lets the cart always hold a strategy and avoids null checks during checkout.

PercentageDiscountStrategy

Coupon implementation for percentage discounts such as SUMMER10. It validates the rate and computes a rounded percentage of subtotal.

FixedAmountCouponStrategy

Coupon implementation for fixed-value discounts. It caps the discount at subtotal so a coupon cannot make the taxable amount negative.

CartObserver

Observer interface for cart change events. UI counters, analytics, and persistence listeners can subscribe without changing cart logic.

LoggingCartObserver

Small observer implementation used by the demo. It prints the event and current item count whenever the cart changes.

Cart

Composite aggregate of CartItem objects. It merges adds, updates quantities, removes items, stores the active discount strategy, and emits observer notifications.

CheckoutService

Pricing boundary. It snapshots the cart, calculates subtotal, asks the strategy for a discount, computes tax, and returns an immutable order.

Order

Immutable checkout result with item snapshot and money breakdown. It protects historical order pricing from later cart or product changes.

Main

Runnable demonstration that creates products, mutates the cart, applies a percentage coupon, checks out with tax, and prints the totals.

Dry Run

Sample input

Products: headphones at 100.00 and mouse at 50.00. Actions: add headphones x2, add mouse x1, apply SUMMER10 percentage coupon, checkout with 8% tax.

StepCart actionSubtotalDiscountTaxOrder total
1Add headphones x2200.000.000.00Cart updated
2Add mouse x1250.000.000.00Cart updated
3Apply SUMMER10250.0025.000.00Policy selected
4Checkout with 8% tax250.0025.0018.00243.00

Checkout prices a single snapshot: subtotal 250.00, discount 25.00, taxable amount 225.00, tax 18.00, and final total 243.00.

Complexity Analysis

OperationTimeSpaceNote
addProductO(1)O(1)LinkedHashMap lookup by product id, then insert or replace one immutable line item.
updateQuantity / removeProductO(1)O(1)Product id lookup and one map mutation.
items snapshotO(N)O(N)Copies N line items into an immutable list view.
checkoutO(N)O(N)Sums N line items and stores an order snapshot. Simple discount and tax policies are O(1).
notify observersO(M)O(M)Copies and calls M observers for a cart event.

The hot path is checkout over N cart lines. Coupon strategies shown here are constant-time, but a production promotion engine may depend on product categories, customer segments, or external eligibility checks.

Extensibility

New coupon type

Add a new DiscountStrategy implementation, such as buy-one-get-one, category percentage, loyalty points, or threshold discount.

Stacked discounts

Introduce a composite discount strategy that owns a list of strategies and applies them in a documented order with a maximum cap.

Category-based tax

Replace the flat tax rate with a TaxStrategy that reads Product.taxCategory and calculates tax per line item.

Inventory reservation

Add an inventory service at checkout so selected quantities are reserved only when the customer commits to purchase.

Cart persistence

Add a repository or event log that stores cart changes per customer while keeping the in-memory cart model unchanged.

Alternative Designs

Pricing methods directly on Cart

Put subtotal, discount, tax, and total methods on Cart so clients call one object for everything.

Tradeoffs

Simpler for tiny demos but mixes item mutation with pricing rules. Tax, promotions, audit, and payment integration will make Cart too large.

Promotion engine with rules

Replace individual discount classes with a rule engine that evaluates eligibility predicates and reward actions.

Tradeoffs

More flexible for large marketplaces, but overkill for an interview base design and harder to reason about than Strategy.

Mutable CartItem lines

Let CartItem expose a setter for quantity and mutate the same line object in place.

Tradeoffs

Uses fewer allocations, but snapshots become unsafe because external code could still hold a line reference and mutate it after checkout.

Checkout as a Facade over many services

Model checkout as a larger orchestration layer over tax, payment, inventory, shipping, and order repositories.

Tradeoffs

Closer to production, but it can distract from the core LLD question unless the interviewer explicitly asks for external integrations.

Common Mistakes

  • ×

    Putting discount and tax calculations inside Cart, which makes item mutation and pricing policy change for different reasons.

  • ×

    Using double for money and accumulating rounding errors across line items and tax.

  • ×

    Allowing duplicate line items for the same product id, then producing inconsistent quantity updates.

  • ×

    Letting coupon code conditionals grow inside checkout instead of using DiscountStrategy.

  • ×

    Returning mutable cart internals so callers can alter items without observer events or validation.

  • ×

    Creating an order that references the live cart instead of an immutable item and price snapshot.

  • ×

    Applying tax before discount without clarifying the business rule.

Follow-up Interview Questions

QHow would you support multiple coupons on one cart?

Create a composite discount strategy that contains ordered child strategies. It applies each child to the remaining subtotal, records the breakdown, and enforces a cap.

QHow would you add category-specific tax?

Introduce a TaxStrategy that calculates tax per CartItem using Product.taxCategory. CheckoutService depends on the interface, similar to discounts.

QHow do you prevent stale prices if the catalog changes after items are added?

Store a price snapshot on CartItem or validate prices during checkout. For orders, always persist the final item prices used at purchase time.

QWhere should payment authorization live?

Outside this domain model. A payment adapter should consume the produced Order or a checkout quote, authorize payment, then persist and confirm the order.

QWhat happens if inventory is unavailable at checkout?

Checkout should call an inventory reservation service before payment capture. If reservation fails, return a recoverable error and keep the cart editable.

Production Considerations

Money and currency

Use a money type with currency instead of bare BigDecimal. The sample focuses on decimal correctness but omits multi-currency conversion rules.

Promotion audit trail

Record coupon code, strategy name, eligibility result, and discount amount on the order for customer support and finance reconciliation.

Concurrency and sessions

Persist cart state with versioning so two browser tabs cannot overwrite each other silently. Use optimistic locking on cart updates.

External tax engines

Real commerce systems often call external tax services by address, product category, and jurisdiction. Keep that behind a tax strategy or adapter.

Observability

Track coupon usage, checkout failures, abandoned carts, tax-service latency, and average order value to detect business and reliability issues.

What Interviewers Look For

  • Did the candidate keep Cart focused on items and quantity state?

  • Is discount behavior represented by Strategy rather than coupon conditionals?

  • Are money values precise and rounded intentionally?

  • Does checkout produce an immutable order snapshot rather than referencing live cart state?

  • Can the design extend to tax engines, inventory reservation, and payment without rewriting core cart logic?

  • Does the candidate name tradeoffs around coupon stacking and price snapshots?

Quiz

0/5 answered

  1. 1.Why should **CheckoutService** calculate totals instead of **Cart**?

  2. 2.Which pattern best fits coupons like 10% off or 50.00 off?

  3. 3.What does **Cart** model in the Composite pattern?

  4. 4.Why does **Order** copy the item list?

  5. 5.What is the role of **CartObserver**?

Practice Variants

Add category coupons

Intermediate

Implement a DiscountStrategy that gives 15% off only for products in a specified tax or catalog category.

Stack discounts safely

Advanced

Build a composite discount strategy that applies multiple coupons in order and returns a detailed discount breakdown.

Reserve inventory at checkout

Intermediate

Add an inventory reservation collaborator to CheckoutService so checkout fails gracefully when requested quantity is unavailable.

Flashcards

Cheat Sheet

Entities: Product, CartItem, Cart, DiscountStrategy, CartObserver, CheckoutService, Order.

Patterns: Strategy for discounts, Composite for cart lines, Observer for cart events, Factory Method for validated object creation.

Flow: add/update/remove items in Cart → apply a DiscountStrategy → CheckoutService snapshots items → subtotal → discount → tax → immutable Order.

Boundaries: Cart owns item state. CheckoutService owns pricing. Order owns history.

Invariants: one line per product id; positive quantities; non-negative money; discount never exceeds subtotal; order never references mutable cart state.

Complexity: cart mutations O(1), checkout O(N), observer notification O(M).

Extend: new coupon = new strategy; tax rules = tax strategy; stacked coupons = composite strategy; inventory/payment = checkout collaborators.

References

  • BookHead First Design Patterns (Strategy and Observer)Freeman & Robson
  • BookEffective Java — Static factories and BigDecimal guidanceJoshua Bloch
  • DocsRefactoring Guru — Strategy Pattern