Bird
Raised Fist0

Order Management System

LLDmediumInterview freq: high
Choose your preparation mode4 modes available
๐ŸŽฏ
Design an Order Management System - States from Placed to Delivered
40 min ยท medium
medium

Design a system to manage orders with lifecycle states: Placed, Confirmed, Packed, Shipped, Delivered. Orders have unique IDs, customer info, and pricing details. The system must handle concurrent updates to order states and allow extensibility for new states or payment methods.

  • Order states: Placed -> Confirmed -> Packed -> Shipped -> Delivered
  • Each order has a unique orderId, customer details, and total price
  • State transitions must be atomic and thread-safe
  • Pricing includes base price and optional discounts
  • System must allow adding new states without modifying existing classes (Open-Closed Principle)
  • Order state behavior varies and requires encapsulation
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define Order States Using Enum
How would you represent the fixed set of order states in Java to ensure type safety and exhaustive handling?
๐Ÿ’ก Hint
Use Java enum to represent states instead of Strings or integers.
Use enum OrderState {PLACED, CONFIRMED, PACKED, SHIPPED, DELIVERED} - enums provide compile-time type safety and exhaustive switch checks in Java.
Mid: Names enum usage for states. Senior: Explains how enum prevents runtime bugs from invalid state strings and enables exhaustive switch-case with compiler warnings.
โš ๏ธCommon Wrong Answer
Using String constants for states leads to runtime bugs due to typos and no compile-time validation.
Step 2
Model Order as a Class with State Field
How would you design the Order class to hold state and other order details while ensuring encapsulation?
๐Ÿ’ก Hint
Use private fields with synchronized methods to update state atomically.
Order class with private volatile OrderState state; synchronized boolean updateState(OrderState newState) to atomically check and set state ensuring visibility and atomicity.
Mid: Uses synchronized method to update state. Senior: Explains volatile ensures visibility of state changes across threads and synchronized prevents race conditions during state transitions.
โš ๏ธCommon Wrong Answer
Not using volatile or synchronization causes stale reads or race conditions where multiple threads corrupt order state.
Step 3
Encapsulate State Behavior Using Abstract Class
How would you encapsulate behavior specific to each order state to follow Open-Closed Principle?
๐Ÿ’ก Hint
Use an abstract class OrderStateHandler with concrete subclasses per state holding state-specific logic.
abstract class OrderStateHandler with instance fields and methods; concrete subclasses implement state-specific behavior. Abstract class used because it holds instance state and shared code; interface cannot hold state in Java.
Mid: Identifies State pattern usage. Senior: Explains why abstract class is chosen over interface due to shared instance state and partial implementation.
โš ๏ธCommon Wrong Answer
Using interface for state behavior leads to code duplication and inability to hold shared state or default implementations.
Step 4
Implement Thread-Safe State Transitions
How do you ensure that state transitions are thread-safe and consistent when multiple threads attempt to update the same order?
๐Ÿ’ก Hint
Use synchronized methods combined with volatile state field to ensure atomic check-and-set and visibility.
synchronized boolean transitionTo(OrderState newState) method in Order class that atomically checks current state and sets new state; volatile state field ensures visibility across threads.
Mid: Uses synchronized method for atomicity. Senior: Traces thread interleaving where Thread 1 reads state=PLACED, context switch, Thread 2 reads state=PLACED, both proceed causing invalid double transition without synchronization.
โš ๏ธCommon Wrong Answer
Using unsynchronized state updates causes race conditions leading to invalid state sequences and inconsistent order data.
Step 5
Design Pricing Structure with Composition
How would you design the pricing model to allow base price and optional discounts while adhering to SOLID principles?
๐Ÿ’ก Hint
Use composition: Order has a Price object that encapsulates base price and discount calculation.
Price class composed inside Order (composition relationship) with methods getBasePrice(), getDiscount(), getFinalPrice(); composition ensures Price lifecycle tied to Order.
Mid: Uses composition for pricing. Senior: Explains composition lifecycle ownership - Price cannot exist independently of Order, enforcing encapsulation and single responsibility.
โš ๏ธCommon Wrong Answer
Storing price fields directly in Order violates single responsibility and makes discount logic hard to extend or test.
๐Ÿ”Œ
API Design Challenge
Design a RESTful API for clients to create orders, update order states, and query order status while ensuring concurrency safety and extensibility.
  • Use HTTP verbs appropriately: POST for creating orders, PUT/PATCH for state updates, GET for queries.
  • Design idempotent endpoints for state transitions to handle retries safely.
  • Consider optimistic locking or versioning to prevent lost updates in concurrent scenarios.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • Use enum for order states to ensure type safety
  • Order class with volatile state field for visibility
  • Synchronized methods for atomic state transitions
  • Abstract class for state behavior encapsulation with instance state
  • Composition relationship for pricing object lifecycle
Good to Have - 30%
  • State pattern applied for extensibility
  • Clear lifecycle ownership between Order and Price
  • Explanation of volatile and synchronized usage in concurrency
  • Extensible design following Open-Closed Principle
  • Use of unique orderId and encapsulated customer info
Bonus - 10%
  • Support for adding new payment methods without modifying Order
  • Use of immutable objects for customer details
  • Detailed concurrency scenario tracing thread interleaving
  • Use of Java 8+ features like Optional for discounts
  • Unit testability of state handlers
Using String constants for states leads to runtime bugs due to typos and no compile-time validation, causing invalid state transitions and runtime errors.
Not using volatile causes stale reads of order state across threads, leading to inconsistent views of order status.
Not synchronizing state transitions causes race conditions and invalid state sequences, corrupting order lifecycle.
Using interface instead of abstract class for state behavior loses ability to hold shared instance state, resulting in code duplication and harder maintenance.
Storing pricing fields directly in Order violates single responsibility and hinders extensibility, making discount logic hard to test and extend.
โœ…
Model Answer
โ–ผ Reveal

The Order Management System uses a Java enum OrderState to represent order lifecycle states with compile-time safety. The Order class holds a volatile OrderState field to ensure visibility of state changes across threads. State transitions are performed via synchronized methods to guarantee atomic check-and-set semantics, preventing race conditions. The State pattern is implemented using an abstract class OrderStateHandler with concrete subclasses encapsulating state-specific behavior, enabling extensibility without modifying existing code. Pricing is modeled using composition with a Price class owned by Order, encapsulating base price and discount logic. This design follows SOLID principles, ensures concurrency safety, and supports extensibility for new states and payment methods.

  • Use Java enum for order states to ensure compile-time type safety and exhaustive handling
  • Declare Order.state as volatile to guarantee visibility of state changes across threads
  • Synchronize state transition methods in Order to provide atomic check-and-set semantics preventing race conditions
  • Implement State pattern using abstract class OrderStateHandler to encapsulate state behavior and support Open-Closed Principle
  • Use composition to model pricing with a Price class owned by Order, ensuring lifecycle management and single responsibility
  • Use enum OrderState for compile-time safe state representation
  • Order class holds volatile OrderState state field for visibility across threads
  • synchronized methods in Order to atomically update state preventing race conditions
  • abstract class OrderStateHandler encapsulates state-specific behavior with instance state
  • State pattern applied to separate state logic and enable Open-Closed Principle
  • Composition relationship: Order owns Price object lifecycle for pricing details
  • Volatile keyword ensures visibility of state changes to all threads
  • Synchronized methods provide atomic check-and-set semantics for state transitions
  • Avoid String constants for states to prevent runtime bugs and poor IDE support
  • Extensible design allows adding new states or payment methods without modifying existing classes

Practice

(1/5)
1. In designing the Vehicle hierarchy for the booking system's parking module, should Vehicle be an abstract class or an interface, and why?
easy
A. Vehicle should be an interface because it only defines behavior without state.
B. Vehicle should be an abstract class because it holds common state like license plate and enforces shared behavior.
C. Vehicle should be a concrete class to allow direct instantiation of generic vehicles.
D. Vehicle should be an enum to restrict vehicle types strictly.

Solution

  1. Step 1: Identify if common state exists in Vehicle

    Vehicle has shared fields like license plate, size, or type, which require storage and partial implementation.
  2. Step 2: Abstract class fits because it allows shared state and behavior, while interfaces cannot hold instance state.

    Using an abstract class enforces common code reuse and prevents direct instantiation.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Common state + shared behavior -> abstract class [check]
Hint: State needs abstract class; behavior-only needs interface [check]
Common Mistakes:
2. You want to add a WeekendFeeStrategy to calculate parking fees differently on weekends using the current adapter-based design. What is the minimum change required to comply with SOLID principles?
medium
A. Add a boolean flag to Vehicle to indicate weekend and modify ParkingLot to check it.
B. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it where needed without modifying existing classes.
C. Modify the existing FeeStrategy class to add weekend logic with if-else checks.
D. Add weekend fee logic inside the adapter class that adapts Vehicle to ParkingLot.

Solution

  1. Step 1: Identify SOLID principle involved

    Open-Closed Principle (OCP) requires extending behavior without modifying existing code.
  2. Step 2: Evaluate options

    Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it where needed without modifying existing classes. respects OCP by adding a new class implementing FeeStrategy. Add a boolean flag to Vehicle to indicate weekend and modify ParkingLot to check it. modifies Vehicle and ParkingLot, violating OCP. Options A and D modify existing classes.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    New strategy class extends behavior without modifying existing code [check]
Hint: OCP: extend with new class, don't modify existing ones [check]
Common Mistakes:
3. In a multi-threaded logging framework, the Appender class has a writeLog() method that first checks if the log file is open, then writes the log. Without synchronization on writeLog(), which exact thread interleaving causes lost log entries?
medium
A. Thread 1 writes log, Thread 2 waits for Thread 1 to finish, then writes.
B. Thread 1 checks file open -> true, context switch; Thread 2 checks file open -> true; both write simultaneously causing race.
C. Thread 1 closes file after writing; Thread 2 writes before file closes.
D. Thread 1 and Thread 2 both block waiting for file lock, causing deadlock.

Solution

  1. Step 1: Identify check-then-act race condition

    Without synchronization, Thread 1 checks file open and sees true, then context switches before writing. Thread 2 also checks and sees true, both proceed to write concurrently.
  2. Step 2: Why this causes lost logs

    Concurrent writes without synchronization cause race conditions, potentially overwriting or corrupting log entries.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Check-then-act gap -> concurrent writes -> lost logs [check]
Hint: Check file open -> context switch -> both write -> race [check]
Common Mistakes:
4. In a multithreaded LRU Cache, if the put() method is not synchronized, which exact sequence of operations between Thread 1 and Thread 2 can cause the cache to contain duplicate keys or corrupt the doubly linked list?
medium
A. Thread 1 and Thread 2 both call get() simultaneously, causing stale reads of the cache order.
B. Thread 1 updates the value of an existing key while Thread 2 evicts a different key, causing no race condition.
C. Thread 1 removes the tail node while Thread 2 updates the head node, causing inconsistent pointers but no duplicate keys.
D. Thread 1 checks if key exists -> false, context switch; Thread 2 checks if key exists -> false; both insert new nodes for the same key concurrently.

Solution

  1. Step 1: Identify the race condition in put()

    Without synchronization, two threads can both check for key existence and find it missing before either inserts, leading to duplicate nodes for the same key.
  2. Step 2: Trace the exact interleaving

    Thread 1: check key -> false; context switch; Thread 2: check key -> false; both insert new nodes, corrupting the cache state.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Concurrent check-then-act without lock -> duplicate keys inserted [check]
Hint: Check-then-act without lock causes duplicates [check]
Common Mistakes:
5. In a parking lot system, parkVehicle() checks if a spot is available and then marks it occupied. Without synchronized, which exact thread interleaving causes double-booking?
medium
A. Thread 1 checks isAvailable() -> true, context switch before marking occupied; Thread 2 checks isAvailable() -> true, both mark occupied.
B. Thread 1 marks spot occupied, Thread 2 waits and then parks after spot is free.
C. Thread 1 parks vehicle, Thread 2 parks vehicle on a different spot simultaneously.
D. Thread 1 releases spot, Thread 2 parks vehicle immediately after.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 reads spot as available but is preempted before marking it occupied. Thread 2 also reads available and proceeds to mark occupied.
  2. Step 2: Explain how this leads to double-booking

    Both threads believe the spot is free and mark it occupied independently, causing two vehicles to be assigned the same spot simultaneously.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Check-then-act gap without synchronization -> double-booking [check]
Hint: Check-then-act gap: both see available before marking occupied [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