Katkat

Design an Object-Oriented Shopping Cart

AmazonSoftware Engineer · Technical Screen

Last updated

Tests object-oriented design principles, domain modeling for monetary precision, extensible discount strategies, and thread-safe state mutations.

Problem Statement

Design an object-oriented in-memory shopping cart system for an e-commerce platform. The system must support core cart management, robust monetary calculations, flexible discount strategies, and auditable checkout receipts while remaining safe under concurrent user actions.

Requirements

  1. Money Precision: Avoid floating-point arithmetic errors. All currency amounts should be handled using integer cents or an immutable Money value object supporting basic arithmetic and a currency code.
  2. Cart Operations: Support adding items, removing items, and updating item quantities. Quantities must be positive integers.
  3. Extensible Pricing Rules: Implement a discount mechanism using the Strategy pattern so new promotions (e.g., percentage discounts, "Buy X, Get Y Free", flat order-level discounts) can be added without modifying the core cart class.
  4. Auditable Receipt: The cart must generate a detailed checkout summary that lists:
    • Base line-item subtotals
    • Itemized discounts applied (with rule identifiers)
    • Final total amount
  5. Thread Safety: The cart must protect its internal state from race conditions during concurrent modifications (e.g., simultaneous item additions or concurrent checkout and item updates).

Proposed Interface

class Money:
    def __init__(self, amount_in_cents: int, currency: str = "USD"):
        ...

class Item:
    def __init__(self, item_id: str, name: str, price: Money):
        ...

class PromotionRule(ABC):
    @abstractmethod
    def apply(self, cart_snapshot: "CartSnapshot") -> list["AppliedDiscount"]:
        ...

class ShoppingCart:
    def __init__(self, user_id: str):
        ...

    def add_item(self, item: Item, quantity: int) -> None:
        ...

    def remove_item(self, item_id: str) -> None:
        ...

    def update_quantity(self, item_id: str, quantity: int) -> None:
        ...

    def apply_promotions(self, rules: list[PromotionRule]) -> "Receipt":
        ...

Example

# Cart Setup
cart = ShoppingCart(user_id="user_123")
item1 = Item("SKU_A", "Keyboard", Money(5000)) # $50.00
item2 = Item("SKU_B", "Mouse", Money(2500))    # $25.00

cart.add_item(item1, 2) # Subtotal: $100.00
cart.add_item(item2, 1) # Subtotal: $25.00

# Rules: 10% off total if cart exceeds $100
rules = [ThresholdPercentageDiscount(threshold=Money(10000), percentage=10)]
receipt = cart.apply_promotions(rules)

# Output:
# Subtotal: $125.00
# Discounts: [AppliedDiscount(name="10% off over $100", amount=-$12.50)]
# Total: $112.50

Edge Cases to Consider

  • Updating quantity to zero should remove the item or raise a defined validation exception.
  • Adding items with mismatched currencies should raise a validation error.
  • Multiple discounts combining to exceed the total cart value should floor the final total at 0 cents.

Hints

Hint 1

Represent currency as an integer amount in the smallest unit (such as cents) inside an immutable value object to prevent rounding anomalies.

Hint 2

Use the Strategy pattern for promotion rules, taking an immutable snapshot of the cart contents as input to avoid race conditions during calculation.

Hint 3

Synchronize cart operations with a reentrant lock (or thread-safe data structures) so that reads, updates, and receipt generation do not produce inconsistent states.

Follow-up questions

  • How would you handle priority, stacking rules, and coupon conflicts when multiple overlapping promotions apply to the same cart?
  • If item inventory must be reserved upon adding an item to the cart, how would you handle inventory reservation expiration and eventual consistency in a distributed system?
  • How would you model the database schema to store persisted carts and ensure past order receipts remain immutable even if product prices change later?