Bird
Raised Fist0

Design an Online Shopping Cart - Product, Cart, Order, Discount, Coupon OOP

LLDmediumInterview freq: high
Choose your preparation mode4 modes available
๐ŸŽฏ
Design an Online Shopping Cart
40 min ยท medium
medium

Design a system to manage products, shopping carts, orders, discounts, and coupons for an online store. The system must handle multiple users adding/removing products concurrently, apply various discount rules, and ensure order consistency.

  • Products have unique IDs, names, prices, and categories.
  • A Cart can hold multiple products with quantities and must support concurrent updates by multiple threads.
  • Orders are created from carts and have statuses (PLACED, SHIPPED, DELIVERED, CANCELLED).
  • Discounts can be percentage-based or fixed amount and can be combined with coupons.
  • Coupons have codes, expiration dates, and usage limits per user.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define Product and ProductCategory
Design the Product class and a type-safe way to represent product categories.
๐Ÿ’ก Hint
Use enum for categories to ensure compile-time safety and avoid string typos.
Use enum ProductCategory for categories - enums provide type safety and exhaustive switch checks in Java, unlike Strings.
Mid: Uses enum for categories. Senior: Explains how enum prevents runtime bugs from typos and enables switch-case exhaustiveness checks.
โš ๏ธCommon Wrong Answer
Using String for categories leads to runtime bugs due to typos and no IDE support for refactoring or exhaustive checks.
Step 2
Design Cart with Thread-Safe Product Management
Design the Cart class to hold products with quantities and support concurrent add/remove operations.
๐Ÿ’ก Hint
Use ConcurrentHashMap<Product, AtomicInteger> for thread-safe quantity updates and synchronize methods that check-and-update multiple fields atomically.
Use ConcurrentHashMap<Product, AtomicInteger> for product quantities and synchronize public boolean addProduct(Product p, int qty) to atomically check and update quantities.
Mid: Uses ConcurrentHashMap and synchronized methods. Senior: Explains exact thread interleaving where without synchronization, two threads can add the same product simultaneously causing inconsistent quantity.
โš ๏ธCommon Wrong Answer
Using HashMap without synchronization causes race conditions where concurrent updates overwrite each other leading to lost updates.
Step 3
Implement Order with Status Lifecycle
Design the Order class with statuses and ensure status transitions are thread-safe and valid.
๐Ÿ’ก Hint
Use enum OrderStatus and make status field volatile. Synchronize status transition methods to prevent invalid concurrent updates.
Use volatile OrderStatus status and synchronized boolean updateStatus(OrderStatus newStatus) to ensure visibility and atomic valid transitions.
Mid: Uses enum and synchronized method. Senior: Explains how volatile ensures visibility across threads and synchronized prevents race conditions on status updates.
โš ๏ธCommon Wrong Answer
Using non-volatile status field causes threads to see stale status values, leading to invalid order state transitions.
Step 4
Design Discount and Coupon Hierarchy
Design an extensible discount system supporting percentage and fixed amount discounts and coupons with usage limits.
๐Ÿ’ก Hint
Use abstract class Discount with abstract method applyDiscount(double price). Coupon extends Discount and adds usage tracking with AtomicInteger.
abstract class Discount with abstract double applyDiscount(double price) - abstract class holds shared state like discountName; interface cannot hold state in Java. Coupon extends Discount and uses AtomicInteger usageCount for thread-safe usage tracking.
Mid: Uses abstract class for shared state and method. Senior: Explains why interface can't hold discountName state and why AtomicInteger is needed for concurrent usage count increments.
โš ๏ธCommon Wrong Answer
Using interface for Discount with discountName field causes compilation error; using int for usageCount causes race conditions in concurrent coupon usage.
Step 5
Integrate Discounts and Coupons in Cart Pricing
Design how Cart calculates total price applying multiple discounts and coupons in a thread-safe manner.
๐Ÿ’ก Hint
Synchronize total price calculation method to prevent concurrent modifications during calculation. Use defensive copies of discount lists.
public synchronized double calculateTotalPrice() to atomically read product quantities and apply discounts ensuring consistent snapshot.
Mid: Synchronizes total price calculation. Senior: Explains thread interleaving where without synchronization, product quantities or discounts may change mid-calculation causing inconsistent totals.
โš ๏ธCommon Wrong Answer
Calculating total price without synchronization leads to inconsistent totals due to concurrent modifications of cart contents or discounts.
๐Ÿ”Œ
API Design Challenge
Design APIs for adding/removing products to/from cart, applying discounts and coupons, and placing orders with status updates.
  • Design RESTful endpoints with clear resource paths and HTTP methods.
  • Use request validation and error handling for invalid operations like expired coupons or invalid status transitions.
  • Consider concurrency control mechanisms to prevent race conditions during cart updates and order status changes.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • Use enum for ProductCategory to ensure compile-time safety
  • Cart uses ConcurrentHashMap and AtomicInteger for thread-safe quantity updates
  • Synchronize Cart methods that modify product quantities to prevent race conditions
  • Order uses volatile OrderStatus and synchronized updateStatus for thread-safe status transitions
  • abstract class Discount with shared state and Coupon subclass with AtomicInteger usageCount
Good to Have - 30%
  • Defensive copying of discount lists in Cart to avoid concurrent modification exceptions
  • Use of synchronized calculateTotalPrice method in Cart for consistent pricing
  • Clear lifecycle ownership: Cart aggregates Products; Order aggregates Cart snapshot
  • Extensible discount hierarchy following Open/Closed principle
Bonus - 10%
  • Use of Java 8 Streams for price calculation
  • Immutable Product class design
  • Use of Optional for coupon application results
Using String for categories or statuses leads to runtime bugs and no compile-time checks, causing hard-to-debug errors and lack of IDE support.
Using HashMap without synchronization causes lost updates in concurrent scenarios, resulting in inconsistent cart state and incorrect quantities.
Non-volatile status field in Order causes stale reads and invalid state transitions, leading to order processing errors.
Using interface for Discount with fields causes compilation errors in Java, preventing shared state management.
Using int instead of AtomicInteger for usageCount causes race conditions in coupon usage, allowing usage limits to be bypassed under concurrency.
โœ…
Model Answer
โ–ผ Reveal

The design uses Java enums for type-safe categories and statuses, ConcurrentHashMap and AtomicInteger for thread-safe quantity and usage tracking, volatile and synchronized for visibility and atomicity in status updates, and an abstract class hierarchy for discounts enabling shared state and extensibility. Synchronization is applied precisely to methods that require atomicity to ensure consistency under concurrency.

  • Use enum ProductCategory to prevent runtime bugs from string typos and enable exhaustive switch-case checks.
  • Use ConcurrentHashMap<Product, AtomicInteger> in Cart for thread-safe quantity updates and synchronize methods that perform check-and-update operations to avoid race conditions.
  • Declare OrderStatus field as volatile and synchronize status update methods to ensure visibility and atomicity of state transitions.
  • Implement Discount as an abstract class to hold shared state like discountName; extend Coupon from Discount and use AtomicInteger for thread-safe usage count increments.
  • Synchronize Cart's calculateTotalPrice method to prevent inconsistent totals caused by concurrent modifications during price calculation.
  • Use enum ProductCategory for type-safe product categories preventing runtime typo bugs.
  • Cart uses ConcurrentHashMap<Product, AtomicInteger> for thread-safe quantity management.
  • Synchronize addProduct and removeProduct methods in Cart to atomically update quantities and prevent race conditions.
  • OrderStatus is an enum with volatile status field and synchronized updateStatus method to ensure visibility and atomic transitions.
  • abstract class Discount holds shared state and abstract applyDiscount method; Coupon extends Discount with AtomicInteger usageCount for concurrency-safe usage tracking.
  • Cart's calculateTotalPrice method is synchronized to ensure consistent snapshot of products and discounts during price computation.

Practice

(1/5)
1. In designing the Amazon Locker Service, should the Package entity be modeled as an abstract class or an interface, and why?
easy
A. Abstract class, because interfaces cannot have any methods related to package handling.
B. Abstract class, because Package has shared state like tracking info and common behavior.
C. Interface, because Package only defines behaviors and allows multiple inheritance of behavior.
D. Interface, because Package needs to hold mutable state for OTP and expiry.

Solution

  1. Step 1: Analyze Package state and behavior

    Package has shared mutable state such as OTP, expiry time, and tracking info, which requires fields and common method implementations.
  2. Step 2: Abstract class vs interface suitability

    Abstract classes can hold state and provide default behavior, interfaces cannot hold mutable state (prior to Java 8 default methods, but still no fields). Therefore, abstract class fits better.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Shared mutable state -> abstract class needed [check]
Hint: Stateful entities -> abstract class; stateless behaviors -> interface [check]
Common Mistakes:
2. You want to add a WeekendFeeStrategy to your logging framework's billing module without modifying existing classes. Which SOLID principle guides this design and what is the minimum change required?
medium
A. Open/Closed Principle: create new WeekendFeeStrategy class implementing FeeStrategy interface; no existing class changes.
B. Single Responsibility Principle: add weekend logic inside existing FeeStrategy class.
C. Dependency Inversion Principle: invert dependencies by injecting weekend logic into existing classes.
D. Liskov Substitution Principle: override existing FeeStrategy methods to add weekend fees.

Solution

  1. Step 1: Identify relevant SOLID principle

    Open/Closed Principle (OCP) states software entities should be open for extension but closed for modification.
  2. Step 2: Apply OCP to add WeekendFeeStrategy

    Implement a new class WeekendFeeStrategy implementing FeeStrategy interface, so existing classes remain unchanged.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    New class implements interface -> existing classes unchanged -> OCP [check]
Hint: Add new class, no existing changes -> OCP [check]
Common Mistakes:
3. To add a WeekendFeeStrategy for weekend pricing in the current booking system design, which minimal change respects the Open-Closed Principle (OCP)?
medium
A. Modify the existing FeeCalculator class to add weekend logic with if-else conditions.
B. Create a new class implementing FeeStrategy interface for weekend fees and inject it without changing existing classes.
C. Add a boolean flag isWeekend in Booking and handle fees there.
D. Extend FeeCalculator with a subclass that overrides fee calculation for weekends.

Solution

  1. Step 1: Identify OCP requirement

    OCP requires adding new functionality without modifying existing tested code.
  2. Step 2: Evaluate options

    Add a boolean flag isWeekend in Booking and handle fees there. adds weekend flag in Booking, which violates OCP by modifying Booking class. Create a new class implementing FeeStrategy interface for weekend fees and inject it without changing existing classes. creates a new FeeStrategy class, respecting OCP by extension. Modify the existing FeeCalculator class to add weekend logic with if-else conditions. and D modify existing classes, violating OCP.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    New FeeStrategy class -> extend without modifying existing code [check]
Hint: New strategy class -> extend, don't modify [check]
Common Mistakes:
4. The Workflow Engine must support ElectricCar vehicles requiring ChargingSpot compatibility. What is the minimum change to the current design to add this without modifying existing classes?
hard
A. Add charging logic inside Executor class to handle ElectricCar spots dynamically.
B. Modify Vehicle base class to include isElectric flag and add charging logic inside existing Spot classes.
C. Create a new ElectricVehicle interface and modify all existing Vehicle subclasses to implement it.
D. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility.COMPATIBILITY_MAP accordingly.

Solution

  1. Step 1: Identify extensibility requirements

    Adding ElectricCar and ChargingSpot must not modify existing classes to respect OCP and avoid class explosion.
  2. Step 2: Use enums and subclassing

    Adding new enum values and a new ElectricCar subclass extends functionality cleanly; updating compatibility map centralizes spot-vehicle matching without changing existing code.
  3. Step 3: Avoid modifying base classes or Executor

    Modifying Vehicle or Executor violates OCP and risks introducing bugs.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    New enums + subclass + compatibility map update -> minimal, no existing class change [check]
Hint: Add enums + subclass + map update, no existing class change [check]
Common Mistakes:
5. The Elevator System needs to support ElectricCar vehicles requiring ChargingSpot types. What is the minimum change to the current Java design to add this without modifying existing classes?
hard
A. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility.COMPATIBILITY_MAP; no existing classes modified.
B. Modify Vehicle base class to add isElectric flag and add charging logic directly in ElevatorController.
C. Create a new ElectricVehicle interface and modify all existing vehicle classes to implement it.
D. Add a new ChargingSpot class and modify Elevator class to handle charging spots explicitly.

Solution

  1. Step 1: Extend enums and create subclass

    Adding new enum constants for vehicle and spot types allows type-safe extension without changing existing classes.
  2. Step 2: Update compatibility map externally

    Updating SpotCompatibility.COMPATIBILITY_MAP to include ElectricCar and ChargingSpot relations extends behavior without modifying existing class code.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Enum extension plus compatibility map update avoids modifying existing classes [check]
Hint: Extend enums and maps, no existing class changes [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard