Limited time: AI code review, hints, mock interviews, whiteboard analysis, and all Pro features are unlocked. Enroll
⏱️ 28 min read

Order Management System

Difficulty: Intermediate Patterns: State, Builder, Observer, Strategy Asked at: Amazon, Flipkart, Swiggy, Razorpay, PhonePe


Functional Requirements

  1. Place orders - create orders with stock validation and atomic deduction
  2. Cancel orders - cancel with stock restoration (only from PLACED/CONFIRMED)
  3. Status transitions - validated state machine (PLACED→CONFIRMED→SHIPPED→DELIVERED)
  4. Notifications - observers fire on every state change
  5. Query orders - retrieve by ID or by customer

Non-Functional Requirements

  1. Thread-safety - order placement and cancellation lock stock deductions
  2. State integrity - invalid transitions are rejected (no skipping states)
  3. Extensibility - new discount strategies and notification channels without modifying service
  4. Correctness - stock deducted on place, restored on cancel, always consistent

Core Entities

Entity Description
OrderStatus Enum - PLACED, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
Customer Immutable - id, name, email
Product SKU with price and thread-safe stock management
OrderItem Links a product to a quantity with price snapshot at order time
Order Aggregates customer, items, status, total; built via Builder pattern
OrderStateValidator Encodes valid state transitions as a directed graph
OrderObserver Interface for notification on status changes
DiscountStrategy Interface for pluggable pricing/discount logic
OrderService Facade - orchestrates placement, cancellation, transitions, queries

Class Diagram

classDiagram
    class OrderStatus {
        <<enumeration>>
        PLACED
        CONFIRMED
        SHIPPED
        DELIVERED
        CANCELLED
    }

    class Customer {
        -String id
        -String name
        -String email
        +getId() String
        +getName() String
        +getEmail() String
    }

    class Product {
        -String sku
        -String name
        -double price
        -int stock
        +deductStock(int qty)
        +restoreStock(int qty)
        +getStock() int
    }

    class OrderItem {
        -Product product
        -int quantity
        -double priceAtOrder
        +getSubtotal() double
    }

    class Order {
        -String id
        -Customer customer
        -List~OrderItem~ items
        -OrderStatus status
        -double totalAmount
        -LocalDateTime createdAt
        -LocalDateTime updatedAt
        +getStatus() OrderStatus
        +transitionTo(OrderStatus newStatus)
    }

    class OrderStateValidator {
        +canTransition(OrderStatus from to OrderStatus to) boolean
        +getValidTransitions(OrderStatus from) Set of OrderStatus
    }

    class OrderObserver {
        <<interface>>
        +onStatusChange(Order order and OrderStatus oldStatus and OrderStatus newStatus)
    }

    class EmailNotifier {
        +onStatusChange(Order and OrderStatus and OrderStatus)
    }

    class SMSNotifier {
        +onStatusChange(Order and OrderStatus and OrderStatus)
    }

    class DiscountStrategy {
        <<interface>>
        +applyDiscount(double amount) double
    }

    class NoDiscount {
        +applyDiscount(double amount) double
    }

    class PercentageDiscount {
        -double percentage
        +applyDiscount(double amount) double
    }

    class OrderService {
        -Map~String and Order~ orders
        -List~OrderObserver~ observers
        -OrderStateValidator validator
        -DiscountStrategy discountStrategy
        -ReentrantLock lock
        +placeOrder(Customer and List~OrderItem~) Order
        +cancelOrder(String orderId)
        +updateStatus(String orderId and OrderStatus newStatus)
        +getOrder(String orderId) Order
        +getOrdersByCustomer(String customerId) List~Order~
    }

    Order --> OrderStatus
    Order --> Customer
    Order --> OrderItem
    OrderItem --> Product
    OrderService --> Order
    OrderService --> OrderStateValidator
    OrderService --> OrderObserver
    OrderService --> DiscountStrategy
    OrderObserver <|.. EmailNotifier
    OrderObserver <|.. SMSNotifier
    DiscountStrategy <|.. NoDiscount
    DiscountStrategy <|.. PercentageDiscount

Design Patterns

Pattern Where Why
State OrderStateValidator encodes transitions Enforces valid lifecycle paths - no illegal state jumps
Builder Order.Builder Assembles complex orders fluently with validation on build()
Observer OrderObserver with Email/SMS notifiers Decouple notifications from order logic; add channels without modifying service
Strategy DiscountStrategy with NoDiscount/PercentageDiscount Swap pricing logic at runtime without changing placement code

How It All Fits Together

Here’s what happens when placeOrder is called:

  1. OrderService acquires the order lock - ensures no concurrent order can race on stock
  2. Validates stock availability for ALL items before any deduction - fail fast if any product has insufficient stock
  3. Deducts stock atomically for each item - if validation passed, all deductions will succeed
  4. Applies the discount strategy to compute the final total from item subtotals
  5. Builds the Order using the Builder pattern - sets id, customer, items, and computed total
  6. Stores the order and notifies all registered observers - EmailNotifier, SMSNotifier, etc. fire
  7. Returns the created order; lock is released - other threads can now proceed

For cancellation, the flow is reversed: validate transition is legal (only from PLACED/CONFIRMED), restore stock for each item, set status to CANCELLED, notify observers.


Complete Code

OrderStatus and Domain Models

OrderStatus is the enum defining all possible states. Customer, Product, and OrderItem are the foundational domain objects. Product has thread-safe stock operations with its own lock for fine-grained concurrency.

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
import java.time.LocalDateTime;

// --- Enums ---

enum OrderStatus {
    PLACED, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
}

// --- Domain Models ---

class Customer {
    private final String id;
    private final String name;
    private final String email;

    public Customer(String id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public String getId() { return id; }
    public String getName() { return name; }
    public String getEmail() { return email; }
}

class Product {
    private final String sku;
    private final String name;
    private final double price;
    private int stock;
    private final ReentrantLock lock = new ReentrantLock();

    public Product(String sku, String name, double price, int stock) {
        this.sku = sku;
        this.name = name;
        this.price = price;
        this.stock = stock;
    }

    public void deductStock(int qty) {
        lock.lock();
        try {
            if (stock < qty) {
                throw new IllegalStateException("Insufficient stock for " + sku
                    + ". Available=" + stock + " Requested=" + qty);
            }
            stock -= qty;
        } finally {
            lock.unlock();
        }
    }

    public void restoreStock(int qty) {
        lock.lock();
        try {
            stock += qty;
        } finally {
            lock.unlock();
        }
    }

    public int getStock() {
        lock.lock();
        try { return stock; } finally { lock.unlock(); }
    }

    public String getSku() { return sku; }
    public String getName() { return name; }
    public double getPrice() { return price; }
}

class OrderItem {
    private final Product product;
    private final int quantity;
    private final double priceAtOrder;

    public OrderItem(Product product, int quantity) {
        this.product = product;
        this.quantity = quantity;
        this.priceAtOrder = product.getPrice();
    }

    public double getSubtotal() { return priceAtOrder * quantity; }
    public Product getProduct() { return product; }
    public int getQuantity() { return quantity; }
    public double getPriceAtOrder() { return priceAtOrder; }
}
import threading
from enum import Enum
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
from abc import ABC, abstractmethod


# --- Enums ---

class OrderStatus(Enum):
    PLACED = "PLACED"
    CONFIRMED = "CONFIRMED"
    SHIPPED = "SHIPPED"
    DELIVERED = "DELIVERED"
    CANCELLED = "CANCELLED"


# --- Domain Models ---

@dataclass
class Customer:
    id: str
    name: str
    email: str


class Product:
    def __init__(self, sku: str, name: str, price: float, stock: int):
        self.sku = sku
        self.name = name
        self.price = price
        self._stock = stock
        self._lock = threading.Lock()

    def deduct_stock(self, qty: int) -> None:
        with self._lock:
            if self._stock < qty:
                raise ValueError(f"Insufficient stock for {self.sku}. "
                                 f"Available={self._stock} Requested={qty}")
            self._stock -= qty

    def restore_stock(self, qty: int) -> None:
        with self._lock:
            self._stock += qty

    @property
    def stock(self) -> int:
        with self._lock:
            return self._stock


@dataclass
class OrderItem:
    product: Product
    quantity: int
    price_at_order: float = 0.0

    def __post_init__(self):
        self.price_at_order = self.product.price

    @property
    def subtotal(self) -> float:
        return self.price_at_order * self.quantity
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#include <memory>
#include <stdexcept>
#include <algorithm>
#include <numeric>
#include <functional>

// --- Enums ---

enum class OrderStatus { PLACED, CONFIRMED, SHIPPED, DELIVERED, CANCELLED };

std::string statusToString(OrderStatus s) {
    switch (s) {
        case OrderStatus::PLACED: return "PLACED";
        case OrderStatus::CONFIRMED: return "CONFIRMED";
        case OrderStatus::SHIPPED: return "SHIPPED";
        case OrderStatus::DELIVERED: return "DELIVERED";
        case OrderStatus::CANCELLED: return "CANCELLED";
    }
    return "UNKNOWN";
}

// --- Domain Models ---

struct Customer {
    std::string id;
    std::string name;
    std::string email;
};

class Product {
private:
    std::string sku_;
    std::string name_;
    double price_;
    int stock_;
    mutable std::mutex mtx_;

public:
    Product(const std::string& sku, const std::string& name, double price, int stock)
        : sku_(sku), name_(name), price_(price), stock_(stock) {}

    void deductStock(int qty) {
        std::lock_guard<std::mutex> guard(mtx_);
        if (stock_ < qty)
            throw std::runtime_error("Insufficient stock for " + sku_);
        stock_ -= qty;
    }

    void restoreStock(int qty) {
        std::lock_guard<std::mutex> guard(mtx_);
        stock_ += qty;
    }

    int getStock() const {
        std::lock_guard<std::mutex> guard(mtx_);
        return stock_;
    }

    const std::string& getSku() const { return sku_; }
    const std::string& getName() const { return name_; }
    double getPrice() const { return price_; }
};

struct OrderItem {
    Product* product;
    int quantity;
    double priceAtOrder;

    OrderItem(Product* p, int qty) : product(p), quantity(qty), priceAtOrder(p->getPrice()) {}
    double getSubtotal() const { return priceAtOrder * quantity; }
};
// --- Enums ---

const OrderStatus = Object.freeze({
    PLACED: 'PLACED',
    CONFIRMED: 'CONFIRMED',
    SHIPPED: 'SHIPPED',
    DELIVERED: 'DELIVERED',
    CANCELLED: 'CANCELLED'
});

// --- Domain Models ---

class Customer {
    constructor(id, name, email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
}

class Product {
    #sku; #name; #price; #stock;

    constructor(sku, name, price, stock) {
        this.#sku = sku;
        this.#name = name;
        this.#price = price;
        this.#stock = stock;
    }

    deductStock(qty) {
        if (this.#stock < qty) {
            throw new Error(`Insufficient stock for ${this.#sku}. Available=${this.#stock} Requested=${qty}`);
        }
        this.#stock -= qty;
    }

    restoreStock(qty) { this.#stock += qty; }
    get stock() { return this.#stock; }
    get sku() { return this.#sku; }
    get name() { return this.#name; }
    get price() { return this.#price; }
}

class OrderItem {
    constructor(product, quantity) {
        this.product = product;
        this.quantity = quantity;
        this.priceAtOrder = product.price;
    }

    get subtotal() { return this.priceAtOrder * this.quantity; }
}

Order

The Order class uses the Builder pattern to construct complex order objects with validation. The builder ensures that an order always has an id, customer, and at least one item before it can be built.

πŸ’‘ Builder pattern = separate the construction of a complex object from its representation. Here it prevents partially-constructed orders from escaping into the system. The fluent API makes order assembly readable and the build() method acts as a final validation gate.

class Order {
    private final String id;
    private final Customer customer;
    private final List<OrderItem> items;
    private OrderStatus status;
    private final double totalAmount;
    private final LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    private Order(Builder builder) {
        this.id = builder.id;
        this.customer = builder.customer;
        this.items = Collections.unmodifiableList(builder.items);
        this.status = OrderStatus.PLACED;
        this.totalAmount = builder.totalAmount;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = this.createdAt;
    }

    public void setStatus(OrderStatus status) {
        this.status = status;
        this.updatedAt = LocalDateTime.now();
    }

    public String getId() { return id; }
    public Customer getCustomer() { return customer; }
    public List<OrderItem> getItems() { return items; }
    public OrderStatus getStatus() { return status; }
    public double getTotalAmount() { return totalAmount; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public LocalDateTime getUpdatedAt() { return updatedAt; }

    @Override
    public String toString() {
        return "Order{id=" + id + " status=" + status + " total=$" + totalAmount + "}";
    }

    // Builder pattern
    static class Builder {
        private String id;
        private Customer customer;
        private List<OrderItem> items = new ArrayList<>();
        private double totalAmount;

        public Builder id(String id) { this.id = id; return this; }
        public Builder customer(Customer customer) { this.customer = customer; return this; }
        public Builder addItem(OrderItem item) { this.items.add(item); return this; }
        public Builder totalAmount(double amount) { this.totalAmount = amount; return this; }
        public Order build() {
            if (id == null || customer == null || items.isEmpty()) {
                throw new IllegalStateException("Order requires id, customer, and at least one item");
            }
            return new Order(this);
        }
    }
}
class Order:
    def __init__(self, order_id: str, customer: Customer, items: list,
                 total_amount: float):
        self.id = order_id
        self.customer = customer
        self.items = list(items)
        self.status = OrderStatus.PLACED
        self.total_amount = total_amount
        self.created_at = datetime.now()
        self.updated_at = self.created_at

    def set_status(self, status: OrderStatus) -> None:
        self.status = status
        self.updated_at = datetime.now()

    def __str__(self) -> str:
        return f"Order{{id={self.id} status={self.status.value} total=${self.total_amount:.2f}}}"
class Order {
public:
    std::string id;
    Customer customer;
    std::vector<OrderItem> items;
    OrderStatus status;
    double totalAmount;

    Order(const std::string& id, const Customer& customer,
          const std::vector<OrderItem>& items, double total)
        : id(id), customer(customer), items(items), status(OrderStatus::PLACED),
          totalAmount(total) {}

    std::string toString() const {
        return "Order{id=" + id + " status=" + statusToString(status)
               + " total=$" + std::to_string(totalAmount) + "}";
    }
};
class Order {
    constructor(id, customer, items, totalAmount) {
        this.id = id;
        this.customer = customer;
        this.items = [...items];
        this.status = OrderStatus.PLACED;
        this.totalAmount = totalAmount;
        this.createdAt = new Date();
        this.updatedAt = this.createdAt;
    }

    setStatus(status) {
        this.status = status;
        this.updatedAt = new Date();
    }

    toString() {
        return `Order{id=${this.id} status=${this.status} total=$${this.totalAmount.toFixed(2)}}`;
    }
}

OrderStateValidator

The state machine is encoded as a map from each status to its set of valid next statuses. This makes it trivial to add new states or modify transitions without touching order logic.

πŸ’‘ Encoding the state machine as a data structure (map of transitions) rather than a chain of if/else means adding a new state like REFUNDED only requires adding one map entry β€” zero changes to existing validation logic.

class OrderStateValidator {
    private static final Map<OrderStatus, Set<OrderStatus>> TRANSITIONS = new EnumMap<>(OrderStatus.class);

    static {
        TRANSITIONS.put(OrderStatus.PLACED, EnumSet.of(OrderStatus.CONFIRMED, OrderStatus.CANCELLED));
        TRANSITIONS.put(OrderStatus.CONFIRMED, EnumSet.of(OrderStatus.SHIPPED, OrderStatus.CANCELLED));
        TRANSITIONS.put(OrderStatus.SHIPPED, EnumSet.of(OrderStatus.DELIVERED));
        TRANSITIONS.put(OrderStatus.DELIVERED, EnumSet.noneOf(OrderStatus.class));
        TRANSITIONS.put(OrderStatus.CANCELLED, EnumSet.noneOf(OrderStatus.class));
    }

    public boolean canTransition(OrderStatus from, OrderStatus to) {
        Set<OrderStatus> valid = TRANSITIONS.get(from);
        return valid != null && valid.contains(to);
    }

    public Set<OrderStatus> getValidTransitions(OrderStatus from) {
        return Collections.unmodifiableSet(TRANSITIONS.getOrDefault(from, EnumSet.noneOf(OrderStatus.class)));
    }
}
class OrderStateValidator:
    _TRANSITIONS: dict[OrderStatus, set[OrderStatus]] = {
        OrderStatus.PLACED: {OrderStatus.CONFIRMED, OrderStatus.CANCELLED},
        OrderStatus.CONFIRMED: {OrderStatus.SHIPPED, OrderStatus.CANCELLED},
        OrderStatus.SHIPPED: {OrderStatus.DELIVERED},
        OrderStatus.DELIVERED: set(),
        OrderStatus.CANCELLED: set(),
    }

    def can_transition(self, from_status: OrderStatus, to_status: OrderStatus) -> bool:
        valid = self._TRANSITIONS.get(from_status, set())
        return to_status in valid

    def get_valid_transitions(self, from_status: OrderStatus) -> set[OrderStatus]:
        return self._TRANSITIONS.get(from_status, set())
class OrderStateValidator {
private:
    std::unordered_map<int, std::vector<OrderStatus>> transitions_;

public:
    OrderStateValidator() {
        transitions_[(int)OrderStatus::PLACED] = {OrderStatus::CONFIRMED, OrderStatus::CANCELLED};
        transitions_[(int)OrderStatus::CONFIRMED] = {OrderStatus::SHIPPED, OrderStatus::CANCELLED};
        transitions_[(int)OrderStatus::SHIPPED] = {OrderStatus::DELIVERED};
        transitions_[(int)OrderStatus::DELIVERED] = {};
        transitions_[(int)OrderStatus::CANCELLED] = {};
    }

    bool canTransition(OrderStatus from, OrderStatus to) const {
        auto it = transitions_.find((int)from);
        if (it == transitions_.end()) return false;
        auto& v = it->second;
        return std::find(v.begin(), v.end(), to) != v.end();
    }
};
class OrderStateValidator {
    #transitions = {
        [OrderStatus.PLACED]: new Set([OrderStatus.CONFIRMED, OrderStatus.CANCELLED]),
        [OrderStatus.CONFIRMED]: new Set([OrderStatus.SHIPPED, OrderStatus.CANCELLED]),
        [OrderStatus.SHIPPED]: new Set([OrderStatus.DELIVERED]),
        [OrderStatus.DELIVERED]: new Set(),
        [OrderStatus.CANCELLED]: new Set()
    };

    canTransition(from, to) {
        const valid = this.#transitions[from];
        return valid ? valid.has(to) : false;
    }

    getValidTransitions(from) {
        return this.#transitions[from] || new Set();
    }
}

Observer Interface and Implementations

OrderObserver defines the contract for reacting to status changes. EmailNotifier and SMSNotifier are two implementations - adding a PushNotifier or WebhookNotifier requires zero changes to OrderService.

interface OrderObserver {
    void onStatusChange(Order order, OrderStatus oldStatus, OrderStatus newStatus);
}

class EmailNotifier implements OrderObserver {
    @Override
    public void onStatusChange(Order order, OrderStatus oldStatus, OrderStatus newStatus) {
        System.out.println("  [Email] Order " + order.getId() + " moved from "
                           + oldStatus + " to " + newStatus
                           + " | Notifying: " + order.getCustomer().getEmail());
    }
}

class SMSNotifier implements OrderObserver {
    @Override
    public void onStatusChange(Order order, OrderStatus oldStatus, OrderStatus newStatus) {
        System.out.println("  [SMS] Order " + order.getId() + ": " + oldStatus + " -> " + newStatus);
    }
}
class OrderObserver(ABC):
    @abstractmethod
    def on_status_change(self, order: Order, old_status: Optional[OrderStatus],
                         new_status: OrderStatus) -> None:
        pass


class EmailNotifier(OrderObserver):
    def on_status_change(self, order, old_status, new_status):
        print(f"  [Email] Order {order.id} moved from {old_status} to {new_status.value}"
              f" | Notifying: {order.customer.email}")


class SMSNotifier(OrderObserver):
    def on_status_change(self, order, old_status, new_status):
        print(f"  [SMS] Order {order.id}: {old_status} -> {new_status.value}")
class OrderObserver {
public:
    virtual ~OrderObserver() = default;
    virtual void onStatusChange(const Order& order, OrderStatus oldStatus, OrderStatus newStatus) = 0;
};

class EmailNotifier : public OrderObserver {
public:
    void onStatusChange(const Order& order, OrderStatus oldStatus, OrderStatus newStatus) override {
        std::cout << "  [Email] Order " << order.id << " moved from "
                  << statusToString(oldStatus) << " to " << statusToString(newStatus)
                  << " | Notifying: " << order.customer.email << std::endl;
    }
};

class SMSNotifier : public OrderObserver {
public:
    void onStatusChange(const Order& order, OrderStatus oldStatus, OrderStatus newStatus) override {
        std::cout << "  [SMS] Order " << order.id << ": "
                  << statusToString(oldStatus) << " -> " << statusToString(newStatus) << std::endl;
    }
};
class EmailNotifier {
    onStatusChange(order, oldStatus, newStatus) {
        console.log(`  [Email] Order ${order.id} moved from ${oldStatus} to ${newStatus}` +
                    ` | Notifying: ${order.customer.email}`);
    }
}

class SMSNotifier {
    onStatusChange(order, oldStatus, newStatus) {
        console.log(`  [SMS] Order ${order.id}: ${oldStatus} -> ${newStatus}`);
    }
}

DiscountStrategy

The Strategy interface for pricing. NoDiscount passes through the amount unchanged; PercentageDiscount applies a percentage reduction. You can add FlatDiscount, CouponDiscount, or TieredDiscount without touching OrderService.

πŸ’‘ Strategy pattern = define a family of algorithms, encapsulate each one, and make them interchangeable at runtime. OrderService doesn’t know or care which discount is active β€” it just calls applyDiscount() and gets back the final price.

interface DiscountStrategy {
    double applyDiscount(double amount);
}

class NoDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double amount) { return amount; }
}

class PercentageDiscount implements DiscountStrategy {
    private final double percentage;

    public PercentageDiscount(double percentage) {
        if (percentage < 0 || percentage > 100) throw new IllegalArgumentException("Invalid percentage");
        this.percentage = percentage;
    }

    @Override
    public double applyDiscount(double amount) {
        return amount * (1 - percentage / 100.0);
    }
}
class DiscountStrategy(ABC):
    @abstractmethod
    def apply_discount(self, amount: float) -> float:
        pass


class NoDiscount(DiscountStrategy):
    def apply_discount(self, amount: float) -> float:
        return amount


class PercentageDiscount(DiscountStrategy):
    def __init__(self, percentage: float):
        if not 0 <= percentage <= 100:
            raise ValueError("Invalid percentage")
        self._percentage = percentage

    def apply_discount(self, amount: float) -> float:
        return amount * (1 - self._percentage / 100.0)
class DiscountStrategy {
public:
    virtual ~DiscountStrategy() = default;
    virtual double applyDiscount(double amount) = 0;
};

class NoDiscount : public DiscountStrategy {
public:
    double applyDiscount(double amount) override { return amount; }
};

class PercentageDiscount : public DiscountStrategy {
    double percentage_;
public:
    PercentageDiscount(double pct) : percentage_(pct) {}
    double applyDiscount(double amount) override {
        return amount * (1.0 - percentage_ / 100.0);
    }
};
class NoDiscount {
    applyDiscount(amount) { return amount; }
}

class PercentageDiscount {
    #percentage;
    constructor(percentage) { this.#percentage = percentage; }
    applyDiscount(amount) { return amount * (1 - this.#percentage / 100); }
}

OrderService

The main service that orchestrates order placement, cancellation, status updates, and queries. Uses a ReentrantLock (or mutex/threading lock) to ensure thread-safe stock operations. Delegates state validation to OrderStateValidator and notifications to registered observers.

class OrderService {
    private final Map<String, Order> orders = new ConcurrentHashMap<>();
    private final List<OrderObserver> observers = new CopyOnWriteArrayList<>();
    private final OrderStateValidator validator = new OrderStateValidator();
    private DiscountStrategy discountStrategy;
    private final ReentrantLock orderLock = new ReentrantLock();
    private int orderCounter = 0;

    public OrderService(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }

    public void addObserver(OrderObserver observer) {
        observers.add(observer);
    }

    public void setDiscountStrategy(DiscountStrategy strategy) {
        this.discountStrategy = strategy;
    }

    public Order placeOrder(Customer customer, List<OrderItem> items) {
        if (customer == null) throw new IllegalArgumentException("Customer is required");
        if (items == null || items.isEmpty()) throw new IllegalArgumentException("At least one item required");

        orderLock.lock();
        try {
            // Validate stock for all items first
            for (OrderItem item : items) {
                if (item.getProduct().getStock() < item.getQuantity()) {
                    throw new IllegalStateException("Insufficient stock for product: "
                        + item.getProduct().getSku());
                }
            }

            // Deduct stock atomically
            for (OrderItem item : items) {
                item.getProduct().deductStock(item.getQuantity());
            }

            // Calculate total with discount
            double subtotal = items.stream().mapToDouble(OrderItem::getSubtotal).sum();
            double total = discountStrategy.applyDiscount(subtotal);

            // Build order
            String orderId = "ORD-" + (++orderCounter);
            Order order = new Order.Builder()
                    .id(orderId)
                    .customer(customer)
                    .totalAmount(total)
                    .build();
            // Add items via builder workaround (items already in builder)
            Order finalOrder = new Order.Builder()
                    .id(orderId)
                    .customer(customer)
                    .totalAmount(total)
                    .build();

            // Rebuild with items
            Order.Builder builder = new Order.Builder().id(orderId).customer(customer).totalAmount(total);
            for (OrderItem item : items) {
                builder.addItem(item);
            }
            finalOrder = builder.build();

            orders.put(orderId, finalOrder);
            notifyObservers(finalOrder, null, OrderStatus.PLACED);
            return finalOrder;
        } finally {
            orderLock.unlock();
        }
    }

    public void cancelOrder(String orderId) {
        orderLock.lock();
        try {
            Order order = getOrderOrThrow(orderId);
            OrderStatus oldStatus = order.getStatus();

            if (!validator.canTransition(oldStatus, OrderStatus.CANCELLED)) {
                throw new IllegalStateException("Cannot cancel order in state: " + oldStatus
                    + ". Cancellation only allowed from PLACED or CONFIRMED.");
            }

            // Restore stock
            for (OrderItem item : order.getItems()) {
                item.getProduct().restoreStock(item.getQuantity());
            }

            order.setStatus(OrderStatus.CANCELLED);
            notifyObservers(order, oldStatus, OrderStatus.CANCELLED);
        } finally {
            orderLock.unlock();
        }
    }

    public void updateStatus(String orderId, OrderStatus newStatus) {
        orderLock.lock();
        try {
            Order order = getOrderOrThrow(orderId);
            OrderStatus oldStatus = order.getStatus();

            if (!validator.canTransition(oldStatus, newStatus)) {
                throw new IllegalStateException("Invalid transition: " + oldStatus + " -> " + newStatus
                    + ". Valid transitions: " + validator.getValidTransitions(oldStatus));
            }

            order.setStatus(newStatus);
            notifyObservers(order, oldStatus, newStatus);
        } finally {
            orderLock.unlock();
        }
    }

    public Order getOrder(String orderId) {
        return getOrderOrThrow(orderId);
    }

    public List<Order> getOrdersByCustomer(String customerId) {
        List<Order> result = new ArrayList<>();
        for (Order order : orders.values()) {
            if (order.getCustomer().getId().equals(customerId)) {
                result.add(order);
            }
        }
        return result;
    }

    private Order getOrderOrThrow(String orderId) {
        Order order = orders.get(orderId);
        if (order == null) throw new IllegalArgumentException("Order not found: " + orderId);
        return order;
    }

    private void notifyObservers(Order order, OrderStatus oldStatus, OrderStatus newStatus) {
        for (OrderObserver observer : observers) {
            observer.onStatusChange(order, oldStatus, newStatus);
        }
    }
}
class OrderService:
    def __init__(self, discount_strategy: DiscountStrategy):
        self._orders: dict[str, Order] = {}
        self._observers: list[OrderObserver] = []
        self._validator = OrderStateValidator()
        self._discount_strategy = discount_strategy
        self._lock = threading.Lock()
        self._counter = 0

    def add_observer(self, observer: OrderObserver) -> None:
        self._observers.append(observer)

    def set_discount_strategy(self, strategy: DiscountStrategy) -> None:
        self._discount_strategy = strategy

    def place_order(self, customer: Customer, items: list[OrderItem]) -> Order:
        if not customer:
            raise ValueError("Customer is required")
        if not items:
            raise ValueError("At least one item required")

        with self._lock:
            # Validate stock
            for item in items:
                if item.product.stock < item.quantity:
                    raise ValueError(f"Insufficient stock for {item.product.sku}")

            # Deduct stock
            for item in items:
                item.product.deduct_stock(item.quantity)

            # Calculate total
            subtotal = sum(item.subtotal for item in items)
            total = self._discount_strategy.apply_discount(subtotal)

            # Build order
            self._counter += 1
            order_id = f"ORD-{self._counter}"
            order = Order(order_id, customer, items, total)
            self._orders[order_id] = order

            self._notify_observers(order, None, OrderStatus.PLACED)
            return order

    def cancel_order(self, order_id: str) -> None:
        with self._lock:
            order = self._get_order_or_throw(order_id)
            old_status = order.status

            if not self._validator.can_transition(old_status, OrderStatus.CANCELLED):
                raise ValueError(f"Cannot cancel order in state: {old_status.value}. "
                                 f"Cancellation only allowed from PLACED or CONFIRMED.")

            # Restore stock
            for item in order.items:
                item.product.restore_stock(item.quantity)

            order.set_status(OrderStatus.CANCELLED)
            self._notify_observers(order, old_status, OrderStatus.CANCELLED)

    def update_status(self, order_id: str, new_status: OrderStatus) -> None:
        with self._lock:
            order = self._get_order_or_throw(order_id)
            old_status = order.status

            if not self._validator.can_transition(old_status, new_status):
                valid = self._validator.get_valid_transitions(old_status)
                raise ValueError(f"Invalid transition: {old_status.value} -> {new_status.value}. "
                                 f"Valid: {[s.value for s in valid]}")

            order.set_status(new_status)
            self._notify_observers(order, old_status, new_status)

    def get_order(self, order_id: str) -> Order:
        return self._get_order_or_throw(order_id)

    def get_orders_by_customer(self, customer_id: str) -> list[Order]:
        return [o for o in self._orders.values() if o.customer.id == customer_id]

    def _get_order_or_throw(self, order_id: str) -> Order:
        order = self._orders.get(order_id)
        if not order:
            raise ValueError(f"Order not found: {order_id}")
        return order

    def _notify_observers(self, order: Order, old_status: Optional[OrderStatus],
                          new_status: OrderStatus) -> None:
        for observer in self._observers:
            observer.on_status_change(order, old_status, new_status)
class OrderService {
private:
    std::unordered_map<std::string, std::unique_ptr<Order>> orders_;
    std::vector<OrderObserver*> observers_;
    OrderStateValidator validator_;
    DiscountStrategy* discountStrategy_;
    std::mutex lock_;
    int counter_ = 0;

    void notifyObservers(const Order& order, OrderStatus oldStatus, OrderStatus newStatus) {
        for (auto* obs : observers_) {
            obs->onStatusChange(order, oldStatus, newStatus);
        }
    }

public:
    OrderService(DiscountStrategy* strategy) : discountStrategy_(strategy) {}

    void addObserver(OrderObserver* obs) { observers_.push_back(obs); }
    void setDiscountStrategy(DiscountStrategy* s) { discountStrategy_ = s; }

    Order* placeOrder(const Customer& customer, std::vector<OrderItem> items) {
        std::lock_guard<std::mutex> guard(lock_);

        for (auto& item : items) {
            if (item.product->getStock() < item.quantity)
                throw std::runtime_error("Insufficient stock for " + item.product->getSku());
        }
        for (auto& item : items) {
            item.product->deductStock(item.quantity);
        }

        double subtotal = 0;
        for (auto& item : items) subtotal += item.getSubtotal();
        double total = discountStrategy_->applyDiscount(subtotal);

        std::string orderId = "ORD-" + std::to_string(++counter_);
        auto order = std::make_unique<Order>(orderId, customer, items, total);
        Order* ptr = order.get();
        orders_[orderId] = std::move(order);

        notifyObservers(*ptr, OrderStatus::PLACED, OrderStatus::PLACED);
        return ptr;
    }

    void cancelOrder(const std::string& orderId) {
        std::lock_guard<std::mutex> guard(lock_);
        auto* order = getOrderOrThrow(orderId);
        OrderStatus oldStatus = order->status;

        if (!validator_.canTransition(oldStatus, OrderStatus::CANCELLED)) {
            throw std::runtime_error("Cannot cancel order in state: " + statusToString(oldStatus));
        }

        for (auto& item : order->items) {
            item.product->restoreStock(item.quantity);
        }
        order->status = OrderStatus::CANCELLED;
        notifyObservers(*order, oldStatus, OrderStatus::CANCELLED);
    }

    void updateStatus(const std::string& orderId, OrderStatus newStatus) {
        std::lock_guard<std::mutex> guard(lock_);
        auto* order = getOrderOrThrow(orderId);
        OrderStatus oldStatus = order->status;

        if (!validator_.canTransition(oldStatus, newStatus)) {
            throw std::runtime_error("Invalid transition: " + statusToString(oldStatus)
                                     + " -> " + statusToString(newStatus));
        }
        order->status = newStatus;
        notifyObservers(*order, oldStatus, newStatus);
    }

    Order* getOrder(const std::string& orderId) { return getOrderOrThrow(orderId); }

private:
    Order* getOrderOrThrow(const std::string& orderId) {
        auto it = orders_.find(orderId);
        if (it == orders_.end()) throw std::runtime_error("Order not found: " + orderId);
        return it->second.get();
    }
};
// JS is single-threaded; operations within one event loop tick are atomic.

class OrderService {
    #orders = new Map();
    #observers = [];
    #validator = new OrderStateValidator();
    #discountStrategy;
    #counter = 0;

    constructor(discountStrategy) {
        this.#discountStrategy = discountStrategy;
    }

    addObserver(observer) { this.#observers.push(observer); }
    setDiscountStrategy(strategy) { this.#discountStrategy = strategy; }

    placeOrder(customer, items) {
        if (!customer) throw new Error('Customer is required');
        if (!items || items.length === 0) throw new Error('At least one item required');

        // Validate stock
        for (const item of items) {
            if (item.product.stock < item.quantity) {
                throw new Error(`Insufficient stock for ${item.product.sku}`);
            }
        }

        // Deduct stock
        for (const item of items) {
            item.product.deductStock(item.quantity);
        }

        const subtotal = items.reduce((sum, item) => sum + item.subtotal, 0);
        const total = this.#discountStrategy.applyDiscount(subtotal);

        const orderId = `ORD-${++this.#counter}`;
        const order = new Order(orderId, customer, items, total);
        this.#orders.set(orderId, order);

        this.#notifyObservers(order, null, OrderStatus.PLACED);
        return order;
    }

    cancelOrder(orderId) {
        const order = this.#getOrderOrThrow(orderId);
        const oldStatus = order.status;

        if (!this.#validator.canTransition(oldStatus, OrderStatus.CANCELLED)) {
            throw new Error(`Cannot cancel order in state: ${oldStatus}. ` +
                            `Cancellation only allowed from PLACED or CONFIRMED.`);
        }

        for (const item of order.items) {
            item.product.restoreStock(item.quantity);
        }
        order.setStatus(OrderStatus.CANCELLED);
        this.#notifyObservers(order, oldStatus, OrderStatus.CANCELLED);
    }

    updateStatus(orderId, newStatus) {
        const order = this.#getOrderOrThrow(orderId);
        const oldStatus = order.status;

        if (!this.#validator.canTransition(oldStatus, newStatus)) {
            const valid = [...this.#validator.getValidTransitions(oldStatus)];
            throw new Error(`Invalid transition: ${oldStatus} -> ${newStatus}. Valid: ${valid}`);
        }

        order.setStatus(newStatus);
        this.#notifyObservers(order, oldStatus, newStatus);
    }

    getOrder(orderId) { return this.#getOrderOrThrow(orderId); }

    getOrdersByCustomer(customerId) {
        return [...this.#orders.values()].filter(o => o.customer.id === customerId);
    }

    #getOrderOrThrow(orderId) {
        const order = this.#orders.get(orderId);
        if (!order) throw new Error(`Order not found: ${orderId}`);
        return order;
    }

    #notifyObservers(order, oldStatus, newStatus) {
        for (const obs of this.#observers) {
            obs.onStatusChange(order, oldStatus, newStatus);
        }
    }
}

Main / Demo

Puts it all together: creates products, wires up the service with a discount strategy and observers, then exercises placement, transitions, cancellation, error handling, and strategy switching.

public class OrderManagementDemo {
    public static void main(String[] args) {
        // Setup products
        Product mouse = new Product("SKU-001", "Wireless Mouse", 29.99, 50);
        Product keyboard = new Product("SKU-002", "Mechanical Keyboard", 79.99, 20);

        // Setup service with 10% discount
        OrderService service = new OrderService(new PercentageDiscount(10));
        service.addObserver(new EmailNotifier());
        service.addObserver(new SMSNotifier());

        // Setup customer
        Customer alice = new Customer("C-1", "Alice", "alice@example.com");

        // Place order
        System.out.println("=== Place Order ===");
        List<OrderItem> items = List.of(
            new OrderItem(mouse, 2),
            new OrderItem(keyboard, 1)
        );
        Order order1 = service.placeOrder(alice, items);
        System.out.println("Created: " + order1);
        System.out.println("Mouse stock after order: " + mouse.getStock());

        // Update status through lifecycle
        System.out.println("\n=== Status Transitions ===");
        service.updateStatus(order1.getId(), OrderStatus.CONFIRMED);
        service.updateStatus(order1.getId(), OrderStatus.SHIPPED);
        service.updateStatus(order1.getId(), OrderStatus.DELIVERED);
        System.out.println("Final status: " + order1.getStatus());

        // Place another order and cancel it
        System.out.println("\n=== Cancel Order ===");
        Order order2 = service.placeOrder(alice, List.of(new OrderItem(mouse, 5)));
        System.out.println("Mouse stock after order2: " + mouse.getStock());
        service.cancelOrder(order2.getId());
        System.out.println("Mouse stock after cancel: " + mouse.getStock());
        System.out.println("Order2 status: " + order2.getStatus());

        // Invalid transition
        System.out.println("\n=== Invalid Transition ===");
        try {
            service.updateStatus(order1.getId(), OrderStatus.PLACED);
        } catch (IllegalStateException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // Cannot cancel delivered order
        System.out.println("\n=== Cannot Cancel Delivered ===");
        try {
            service.cancelOrder(order1.getId());
        } catch (IllegalStateException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // Query orders
        System.out.println("\n=== Orders by Customer ===");
        List<Order> aliceOrders = service.getOrdersByCustomer("C-1");
        for (Order o : aliceOrders) {
            System.out.println("  " + o);
        }

        // Switch discount strategy
        System.out.println("\n=== Switch to No Discount ===");
        service.setDiscountStrategy(new NoDiscount());
        Order order3 = service.placeOrder(alice, List.of(new OrderItem(keyboard, 1)));
        System.out.println("Order3 (no discount): " + order3);
    }
}
def main():
    mouse = Product("SKU-001", "Wireless Mouse", 29.99, 50)
    keyboard = Product("SKU-002", "Mechanical Keyboard", 79.99, 20)

    service = OrderService(PercentageDiscount(10))
    service.add_observer(EmailNotifier())
    service.add_observer(SMSNotifier())

    alice = Customer("C-1", "Alice", "alice@example.com")

    # Place order
    print("=== Place Order ===")
    items = [OrderItem(mouse, 2), OrderItem(keyboard, 1)]
    order1 = service.place_order(alice, items)
    print(f"Created: {order1}")
    print(f"Mouse stock after order: {mouse.stock}")

    # Transitions
    print("\n=== Status Transitions ===")
    service.update_status(order1.id, OrderStatus.CONFIRMED)
    service.update_status(order1.id, OrderStatus.SHIPPED)
    service.update_status(order1.id, OrderStatus.DELIVERED)
    print(f"Final status: {order1.status.value}")

    # Cancel
    print("\n=== Cancel Order ===")
    order2 = service.place_order(alice, [OrderItem(mouse, 5)])
    print(f"Mouse stock after order2: {mouse.stock}")
    service.cancel_order(order2.id)
    print(f"Mouse stock after cancel: {mouse.stock}")
    print(f"Order2 status: {order2.status.value}")

    # Invalid transition
    print("\n=== Invalid Transition ===")
    try:
        service.update_status(order1.id, OrderStatus.PLACED)
    except ValueError as e:
        print(f"Caught: {e}")

    # Cannot cancel delivered
    print("\n=== Cannot Cancel Delivered ===")
    try:
        service.cancel_order(order1.id)
    except ValueError as e:
        print(f"Caught: {e}")

    # Query
    print("\n=== Orders by Customer ===")
    for o in service.get_orders_by_customer("C-1"):
        print(f"  {o}")

    # Switch strategy
    print("\n=== Switch to No Discount ===")
    service.set_discount_strategy(NoDiscount())
    order3 = service.place_order(alice, [OrderItem(keyboard, 1)])
    print(f"Order3 (no discount): {order3}")


if __name__ == "__main__":
    main()
int main() {
    Product mouse("SKU-001", "Wireless Mouse", 29.99, 50);
    Product keyboard("SKU-002", "Mechanical Keyboard", 79.99, 20);

    PercentageDiscount discount(10);
    OrderService service(&discount);

    EmailNotifier emailNotifier;
    SMSNotifier smsNotifier;
    service.addObserver(&emailNotifier);
    service.addObserver(&smsNotifier);

    Customer alice{"C-1", "Alice", "alice@example.com"};

    std::cout << "=== Place Order ===" << std::endl;
    std::vector<OrderItem> items = { OrderItem(&mouse, 2), OrderItem(&keyboard, 1) };
    Order* order1 = service.placeOrder(alice, items);
    std::cout << "Created: " << order1->toString() << std::endl;
    std::cout << "Mouse stock: " << mouse.getStock() << std::endl;

    std::cout << "\n=== Status Transitions ===" << std::endl;
    service.updateStatus(order1->id, OrderStatus::CONFIRMED);
    service.updateStatus(order1->id, OrderStatus::SHIPPED);
    service.updateStatus(order1->id, OrderStatus::DELIVERED);
    std::cout << "Final: " << statusToString(order1->status) << std::endl;

    std::cout << "\n=== Cancel Order ===" << std::endl;
    Order* order2 = service.placeOrder(alice, {OrderItem(&mouse, 5)});
    std::cout << "Mouse stock after order2: " << mouse.getStock() << std::endl;
    service.cancelOrder(order2->id);
    std::cout << "Mouse stock after cancel: " << mouse.getStock() << std::endl;

    std::cout << "\n=== Invalid Transition ===" << std::endl;
    try {
        service.updateStatus(order1->id, OrderStatus::PLACED);
    } catch (const std::runtime_error& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }

    std::cout << "\n=== Cannot Cancel Delivered ===" << std::endl;
    try {
        service.cancelOrder(order1->id);
    } catch (const std::runtime_error& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }

    // Switch strategy
    std::cout << "\n=== No Discount ===" << std::endl;
    NoDiscount noDiscount;
    service.setDiscountStrategy(&noDiscount);
    Order* order3 = service.placeOrder(alice, {OrderItem(&keyboard, 1)});
    std::cout << "Order3: " << order3->toString() << std::endl;

    return 0;
}
function main() {
    const mouse = new Product('SKU-001', 'Wireless Mouse', 29.99, 50);
    const keyboard = new Product('SKU-002', 'Mechanical Keyboard', 79.99, 20);

    const service = new OrderService(new PercentageDiscount(10));
    service.addObserver(new EmailNotifier());
    service.addObserver(new SMSNotifier());

    const alice = new Customer('C-1', 'Alice', 'alice@example.com');

    console.log('=== Place Order ===');
    const items = [new OrderItem(mouse, 2), new OrderItem(keyboard, 1)];
    const order1 = service.placeOrder(alice, items);
    console.log(`Created: ${order1.toString()}`);
    console.log(`Mouse stock after order: ${mouse.stock}`);

    console.log('\n=== Status Transitions ===');
    service.updateStatus(order1.id, OrderStatus.CONFIRMED);
    service.updateStatus(order1.id, OrderStatus.SHIPPED);
    service.updateStatus(order1.id, OrderStatus.DELIVERED);
    console.log(`Final status: ${order1.status}`);

    console.log('\n=== Cancel Order ===');
    const order2 = service.placeOrder(alice, [new OrderItem(mouse, 5)]);
    console.log(`Mouse stock after order2: ${mouse.stock}`);
    service.cancelOrder(order2.id);
    console.log(`Mouse stock after cancel: ${mouse.stock}`);
    console.log(`Order2 status: ${order2.status}`);

    console.log('\n=== Invalid Transition ===');
    try {
        service.updateStatus(order1.id, OrderStatus.PLACED);
    } catch (e) {
        console.log(`Caught: ${e.message}`);
    }

    console.log('\n=== Cannot Cancel Delivered ===');
    try {
        service.cancelOrder(order1.id);
    } catch (e) {
        console.log(`Caught: ${e.message}`);
    }

    console.log('\n=== Orders by Customer ===');
    for (const o of service.getOrdersByCustomer('C-1')) {
        console.log(`  ${o.toString()}`);
    }

    console.log('\n=== Switch to No Discount ===');
    service.setDiscountStrategy(new NoDiscount());
    const order3 = service.placeOrder(alice, [new OrderItem(keyboard, 1)]);
    console.log(`Order3 (no discount): ${order3.toString()}`);
}

main();

Follow-up Questions

  1. How would you integrate payment processing? Add a PaymentService with a PaymentStrategy (CreditCard, UPI, Wallet). Order transitions to CONFIRMED only after payment succeeds. Handle payment timeouts with a retry mechanism or mark order as PAYMENT_PENDING.

  2. How would you implement the Saga pattern for distributed transactions? When order placement spans multiple services (inventory, payment, shipping), use a Saga orchestrator that executes compensating transactions on failure. If payment fails after stock deduction, the saga triggers stock restoration.

  3. How would you handle order retry on payment failure? Implement an idempotency key per order attempt. Store payment intent with retry count and exponential backoff. After max retries, transition to PAYMENT_FAILED state and restore stock.

  4. How would you add support for partial cancellation (cancel individual items)? Add an OrderItem-level status. On partial cancel, restore stock for cancelled items, recalculate total. Order remains active if any items are still valid.

  5. How would you implement real-time order tracking? Observer pattern with WebSocket push. Add location events (picked up, in transit, out for delivery) as sub-states of SHIPPED. A TrackingObserver pushes updates to connected clients.

  6. How would you handle flash sale concurrency (1000 orders for 10 items)? Optimistic locking with version numbers on stock, or a queue-based approach where orders are serialized. Use a distributed lock (Redis) for cross-instance coordination. Return β€œsold out” immediately once stock hits zero.

  7. How would you add order history and audit logging? Event sourcing - store every state change as an immutable event (OrderPlaced, OrderConfirmed, etc.). Replay events to rebuild current state. Use a separate read model for query performance.


Scale this design past a single process and these are the concepts it runs into:

Discussion

Newest first
You

Free system design + DSA prep. If it helped you crack an interview, consider supporting.

SensAI SensAI
Beta
Listening...
Tap mic to stop voice mode

Shape what we build next

Every piece of feedback is read by the team and directly influences our roadmap.

What type of feedback?

Install SystemCraft

Add to your home screen for instant access, offline reading, and a distraction-free experience.

Offline reading Faster loads No browser tabs App-like feel

Unlock AI Features

One click to activate - no payment, no credit card. Just sign in and you're in.

AI code review and hints
SensAI chat assistant
AI mock interviews
Whiteboard analysis
100% free during early access