2Brute2Best
🔥0
A4

Order Lifecycle & Cancel Rules

High-frequency interview topic Pro OnlyState std::variant + std::visit
⚡ 60-Second Architecture SummaryA4 · State Pattern
Naive Bottleneck→State Pattern Cure
The Naive Bottleneck: Duplicate enum switches across every method

Representing stages with an `OrderStatus` enum and giant `switch (status_)` statements scattered across every method (`accept`, `startCooking`, `pickUp`, `deliver`, `cancel`) allows illegal transitions (e.g. cancelling an order already out for delivery or delivering an unaccepted order). Adding a new status forces risky edits across half a dozen switch blocks.

Adding a single new state (QualityChecked) forces developers to edit 5 separate switch statements across NaiveOrder.cpp. Forgetting a single case leaves an order stuck in an invalid state.

The Pattern Cure: State Pattern

Allow an object to alter its behavior when its internal state changes, appearing as if the object changed its class.

We replace the OrderStatus enum and switch statements with an OrderState polymorphic interface. Each lifecycle stage (PlacedState, AcceptedState, CookingState, PickedUpState, DeliveredState, CancelledState) is its own class encapsulating its valid transitions. Order delegates all action calls to state_->action(*this), letting states transition the context cleanly.

Sprint 4 of 9 · Order Lifecycle State Machine
Story A — Food Delivery System — Progressive System Build
1. Where our codebase left off
← A3

In Sprint 3 (A3), we decoupled order event broadcasts via the Observer pattern so the kitchen, riders, and customer apps receive real-time notifications.

2. What we are building now

We need to manage the order's entire lifecycle progression: Placed → Accepted → Cooking → PickedUp (Out for Delivery) → Delivered, while enforcing business rules such as allowing cancellation with refund only before cooking begins.

3. The pain point we hit

Representing stages with an OrderStatus enum and giant switch (status_) statements scattered across every method (accept, startCooking, pickUp, deliver, cancel) allows illegal transitions (e.g. cancelling an order already out for delivery or delivering an unaccepted order). Adding a new status forces risky edits across half a dozen switch blocks.

4. What this unlocks next
Next: A5 →

Encapsulating lifecycle rules inside concrete OrderState subclasses (or modern std::variant states) makes illegal transitions unrepresentable and isolates state-specific behavior. Next in Sprint 5 (A5 · Factory Method), once cooking completes and an order enters pickup, we must dispatch the right vehicle type (ExpressBikePartner, EcoBatchPartner, DronePartner) without scattering new across our codebase.

01

The product ticket

Ticket · A4FOOD-104 · Order Lifecycle and State-Dependent Cancellation Rules

Orders progress through a strict multi-stage lifecycle: Placed → Accepted → Cooking → PickedUp → Delivered. Customer cancellation is permitted during early stages (Placed and Accepted), but must be strictly forbidden once food preparation or delivery has started.

Requirements
  • •

    Enforce valid forward transitions: an order cannot be picked up before cooking starts, nor delivered before pickup.

  • •

    Allow customer cancellation in Placed and Accepted stages with full refund.

  • •

    Reject cancellation attempts once the order reaches Cooking, PickedUp, or Delivered stages.

The next requirement that breaks naive code

Operations is adding new intermediate stages next quarter (QualityChecked and Delayed) with customized retry logic and notification timeouts. In the naive codebase, every new state requires hunting down and updating half a dozen switch (status_) blocks across the codebase.

02

Step 1 — The naive code & why it breaks

The order holds an OrderStatus enum, and every lifecycle action (accept(), startCooking(), pickUp(), deliver(), cancel()) executes a giant switch (status_) statement.

Naive design (what not to ship) · naive.cpp
#include <print>
#include <string>

enum class OrderStatus {
    Placed,
    Accepted,
    Cooking,
    PickedUp,
    Delivered,
    Cancelled
};

// Naive Order: every lifecycle method contains a switch over OrderStatus.
// Adding a new state or transition rule requires editing every single method!
class NaiveOrder {
    std::string orderId_;
    OrderStatus status_{OrderStatus::Placed};
    std::string riderId_;
public:
    explicit NaiveOrder(std::string id) : orderId_(std::move(id)) {}

    bool accept() {
        switch (status_) {
            case OrderStatus::Placed:
                status_ = OrderStatus::Accepted;
                std::println("[{}] Restaurant accepted the order.", orderId_);
                return true;
            default:
                std::println("[{}] Cannot accept in current state.", orderId_);
                return false;
        }
    }

    bool startCooking() {
        switch (status_) {
            case OrderStatus::Accepted:
                status_ = OrderStatus::Cooking;
                std::println("[{}] Kitchen started cooking.", orderId_);
                return true;
            default:
                std::println("[{}] Cannot start cooking unless accepted.", orderId_);
                return false;
        }
    }

    bool pickUp(const std::string& riderId) {
        switch (status_) {
            case OrderStatus::Cooking:
                status_ = OrderStatus::PickedUp;
                riderId_ = riderId;
                std::println("[{}] Rider {} picked up the order.", orderId_, riderId_);
                return true;
            default:
                std::println("[{}] Cannot pick up order in current state.", orderId_);
                return false;
        }
    }

    bool deliver() {
        switch (status_) {
            case OrderStatus::PickedUp:
                status_ = OrderStatus::Delivered;
                std::println("[{}] Delivered to customer successfully!", orderId_);
                return true;
            default:
                std::println("[{}] Cannot deliver order unless picked up.", orderId_);
                return false;
        }
    }

    bool cancel() {
        switch (status_) {
            case OrderStatus::Placed:
            case OrderStatus::Accepted:
                status_ = OrderStatus::Cancelled;
                std::println("[{}] Order cancelled. Full refund issued.", orderId_);
                return true;
            case OrderStatus::Cooking:
                std::println("[{}] Cannot cancel: Kitchen is actively preparing food!", orderId_);
                return false;
            case OrderStatus::PickedUp:
                std::println("[{}] Cannot cancel: Rider is already en route!", orderId_);
                return false;
            case OrderStatus::Delivered:
                std::println("[{}] Cannot cancel: Already delivered.", orderId_);
                return false;
            case OrderStatus::Cancelled:
                std::println("[{}] Already cancelled.", orderId_);
                return false;
        }
        return false;
    }

    [[nodiscard]] OrderStatus status() const { return status_; }
};

int main() {
    NaiveOrder order("ORD-401");
    order.accept();
    order.startCooking();
    // Cancellation should fail now because it is already cooking!
    order.cancel();
    order.pickUp("RIDER-7");
    order.deliver();
}
Duplicate enum switches across every method

Adding a single new state (QualityChecked) forces developers to edit 5 separate switch statements across NaiveOrder.cpp. Forgetting a single case leaves an order stuck in an invalid state.

State transitions and rules are scattered

To understand all the behaviors of CookingState, you cannot look at one place. You must read accept(), startCooking(), pickUp(), and cancel() to piece together what Cooking allows and forbids.

State-specific fields pollute the main class

Fields like riderId_ (only needed after pickup) or cancellationReason_ are dumped into NaiveOrder as nullable members, bloating the class and weakening invariants.

03

Step 2 — The fix: State Pattern

Core idea in one sentence

Allow an object to alter its behavior when its internal state changes, appearing as if the object changed its class.

We replace the OrderStatus enum and switch statements with an OrderState polymorphic interface. Each lifecycle stage (PlacedState, AcceptedState, CookingState, PickedUpState, DeliveredState, CancelledState) is its own class encapsulating its valid transitions. Order delegates all action calls to state_->action(*this), letting states transition the context cleanly.

Pattern roles in our story
State Interface

OrderState — declares methods for lifecycle transitions (accept, startCooking, pickUp, deliver, cancel) with safe default rejection handlers.

Concrete States

PlacedState, AcceptedState, CookingState, PickedUpState, DeliveredState, CancelledState — encapsulate the valid actions and next state transitions for each phase.

Context

Order — owns std::unique_ptr<OrderState> state_ and delegates lifecycle actions to the current state.

Brute2Best Pro Content

Unlock Full UML Diagram & C++23 Solution

The interactive UML architecture diagrams, refactored C++23 code, real-world extensions, and interview Q&A are part of Brute2Best Pro. All 7 foundation chapters are 100% free.

All 440 visual dry runs & algorithms
Interactive step-by-step playback with pointer & array state transitions
52 comprehensive LLD chapters
Interactive UML diagrams, real-world stories (Food Delivery, Trading, RPG), and practice solutions
40 C++ basics labs & hardware memory models
Vtables, byte layouts, assembly insights, and multi-threading race conditions
Production-ready C++23 source code
Modern idioms, clean contracts, time/space complexity derivations, and interview takeaways

First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.

Systems Integration

Related C++ Systems & Algorithmic Foundations

Deep dive into underlying layers