Restaurant/Menu/Order/Table/Kitchen classes, order status flow as State pattern, and separating dine-in from takeout/delivery without duplicating logic.
Published September 23, 2026
class Menu { List<MenuItem> items; }
class MenuItem { String name; double price; boolean available; }
class Table { int number; int capacity; TableStatus status; } // dine-in only
class Order {
List<OrderLine> lines;
OrderType type; // DINE_IN, TAKEOUT, DELIVERY
Table table; // null for takeout/delivery
OrderStatus status;
}
interface OrderState {
OrderState advance(Order order); // returns the NEXT state — the transition logic lives per-state
}
class PlacedState implements OrderState {
public OrderState advance(Order order) { notifyKitchen(order); return new PreparingState(); }
}
class PreparingState implements OrderState {
public OrderState advance(Order order) { return new ReadyState(); }
}
class ReadyState implements OrderState {
public OrderState advance(Order order) {
return order.type == OrderType.DINE_IN ? new ServedState() : new AwaitingPickupOrDeliveryState();
}
}
The placed → preparing → ready → served flow is a direct State pattern application (see State Pattern) — each state knows only its own valid next transition, and ReadyState's branch on order.type is the one place order-type-specific flow divergence needs to be expressed, not scattered across the whole class.
Rather than two entirely separate class hierarchies (DineInOrder vs TakeoutOrder), a single Order class with an OrderType field and one branch point (in ReadyState, shown above) keeps the shared 90% of order-handling logic (placement, kitchen notification, line items, pricing) unified, while only the genuinely divergent final step (served to a table vs handed off for pickup/delivery) differs. This is a judgment call worth naming explicitly: full separation into distinct class hierarchies would be over-engineering for a difference this localized — the State pattern's per-state branch is a proportionate amount of structure for one point of real divergence, not the whole flow.
Q: Why does Table exist as a separate class rather than just a table number field on Order? A: Table has its own independent state (capacity, current occupancy status) that needs tracking regardless of any specific order — a restaurant needs to know which tables are free for seating NEW parties, which is a Table-level concern that exists before, during, and after any single order's lifecycle, not something that belongs embedded in Order.
Q: How would delivery add a DeliveryPartner assignment step to this flow, connecting to Food Delivery Order Matching? A: AwaitingPickupOrDeliveryState's advance() for a DELIVERY-type order would trigger a matching call (see Food Delivery Order Matching's MatchingEngine) before transitioning further — this is exactly the LLD-to-HLD connection point that class design worth naming: the class model provides the hook, the actual matching algorithm is a separate concern layered on top.
Q: What happens if the kitchen marks an order ready but a dine-in customer has already left (a real edge case)? A: Worth naming as a scenario requiring an explicit state or handling path (e.g. a 'CancelledAfterReady' resolution, or holding the prepared order for a return), rather than assuming the happy-path transition sequence always completes — a strong interview answer names edge cases like this explicitly even without fully designing the resolution.
Q: Should MenuItem availability (available: boolean) be checked at order-placement time or continuously? A: At minimum, checked at placement time (rejecting an order line for an unavailable item) — a more robust design would also handle an item becoming unavailable mid-preparation (the kitchen discovers it's out of an ingredient), which argues for treating availability changes as another event PlacedState/PreparingState might need to react to, not just a static field checked once.