Limited time: AI code review, hints, mock interviews, whiteboard analysis, and all Pro features are unlocked. Enroll
โฑ๏ธ 33 min read

Restaurant Booking System

Difficulty: Intermediate-Advanced Patterns: Strategy, State, Observer, Repository Asked at: Zomato, Swiggy, OpenTable, Amazon, Google


Functional Requirements

  1. Table management โ€” register tables with capacity and type (Indoor, Outdoor, VIP)
  2. Time-slot reservations โ€” book tables with conflict detection for overlapping slots
  3. Waitlist management โ€” FIFO waitlist per time slot when no tables available
  4. Auto-promotion โ€” cancellation triggers automatic waitlist-to-booking promotion
  5. Booking lifecycle โ€” state machine: CONFIRMED โ†’ SEATED โ†’ COMPLETED (or CANCELLED)

Non-Functional Requirements

  1. Thread-safety โ€” concurrent booking attempts cannot double-book a table
  2. Conflict detection โ€” overlap-based, not just exact time match
  3. Extensibility โ€” swappable table assignment strategies at runtime
  4. State integrity โ€” only valid state transitions allowed

Core Entities

Entity Description
TableType Enum โ€” INDOOR, OUTDOOR, VIP
BookingStatus Enum โ€” CONFIRMED, SEATED, COMPLETED, CANCELLED
TimeSlot Date + start/end time with overlap detection
Table Immutable โ€” id, capacity, type
Booking Customer reservation with state machine transitions
WaitlistEntry FIFO queue entry โ€” customer, party size, time slot, timestamp
TableAssignmentStrategy Interface โ€” pick the best table from available list
FirstFitStrategy Assigns the first available table that fits the party
BestFitStrategy Assigns the smallest available table that fits (minimizes waste)
BookingService Orchestrates booking, cancellation, waitlist promotion with lock

Class Diagram

classDiagram
    class Restaurant {
        -String id
        -String name
        -List tables
        +addTable(int capacity, TableType type) Table
        +getTables() List
    }

    class Table {
        -String id
        -int capacity
        -TableType tableType
        +getId() String
        +getCapacity() int
        +getTableType() TableType
    }

    class TableType {
        <<enumeration>>
        INDOOR
        OUTDOOR
        VIP
    }

    class TimeSlot {
        -LocalDate date
        -LocalTime startTime
        -LocalTime endTime
        +overlapsWith(TimeSlot other) boolean
    }

    class Booking {
        -String id
        -String customerName
        -int partySize
        -Table table
        -TimeSlot timeSlot
        -BookingStatus status
        +confirm()
        +seat()
        +complete()
        +cancel()
    }

    class BookingStatus {
        <<enumeration>>
        CONFIRMED
        SEATED
        COMPLETED
        CANCELLED
    }

    class WaitlistEntry {
        -String customerName
        -int partySize
        -TimeSlot timeSlot
        -LocalDateTime addedAt
    }

    class TableAssignmentStrategy {
        <<interface>>
        +assignTable(List availableTables, int partySize) Table
    }

    class FirstFitStrategy {
        +assignTable(List availableTables, int partySize) Table
    }

    class BestFitStrategy {
        +assignTable(List availableTables, int partySize) Table
    }

    class BookingService {
        -TableRepository tableRepo
        -BookingRepository bookingRepo
        -WaitlistRepository waitlistRepo
        -TableAssignmentStrategy strategy
        -ReentrantLock lock
        +bookTable(String customer, int partySize, TimeSlot slot) BookingResult
        +cancelBooking(String bookingId)
        +getAvailableTables(TimeSlot slot, int partySize) List
        +seatGuests(String bookingId)
        +completeBooking(String bookingId)
        +getWaitlist(TimeSlot slot) List
    }

    class TableRepository {
        <<interface>>
        +save(Table)
        +findById(String) Table
        +findAll() List
    }

    class BookingRepository {
        <<interface>>
        +save(Booking)
        +findById(String) Booking
        +findByTableAndSlot(String tableId, TimeSlot slot) List
    }

    class WaitlistRepository {
        <<interface>>
        +add(WaitlistEntry)
        +getBySlot(TimeSlot) List
        +removeFirst(TimeSlot) WaitlistEntry
    }

    BookingService --> TableRepository
    BookingService --> BookingRepository
    BookingService --> WaitlistRepository
    BookingService --> TableAssignmentStrategy
    TableAssignmentStrategy <|.. FirstFitStrategy
    TableAssignmentStrategy <|.. BestFitStrategy
    Restaurant --> Table
    Table --> TableType
    Booking --> Table
    Booking --> TimeSlot
    Booking --> BookingStatus
    BookingRepository --> Booking
    WaitlistRepository --> WaitlistEntry
    WaitlistEntry --> TimeSlot

Design Patterns

Pattern Where Why
Strategy TableAssignmentStrategy with FirstFit/BestFit Swap table selection algorithm at runtime without changing BookingService
State Booking lifecycle transitions Strict state machine โ€” CONFIRMED โ†’ SEATED โ†’ COMPLETED or CANCELLED. Invalid transitions throw exceptions
Observer Waitlist auto-promotion on cancellation Cancellation implicitly โ€œnotifiesโ€ the waitlist, promoting the next eligible party
Repository Table/Booking/Waitlist repositories Abstract storage behind interfaces โ€” swap InMemory for database-backed without touching business logic

How It All Fits Together

Hereโ€™s what happens when a customer wants to book a table:

  1. Client calls bookTable(customerName, partySize, timeSlot) on BookingService
  2. Service acquires the lock to prevent race conditions
  3. Queries all tables, filters to those with sufficient capacity and no overlapping bookings
  4. Delegates to the TableAssignmentStrategy (FirstFit or BestFit) to pick the best table
  5. If a table is found: creates a Booking in CONFIRMED state, stores it, and returns
  6. If no table is available: creates a WaitlistEntry, adds to FIFO queue for that slot
  7. On cancellation: booking transitions to CANCELLED, then promoteFromWaitlist() automatically tries to seat the next waitlisted party

๐Ÿ’ก The lock ensures that between checking availability and creating the booking, no other thread can sneak in and grab the same table. This is the critical section that prevents double-booking.


Complete Code

Enums and TimeSlot

TableType and BookingStatus are simple enums. TimeSlot is the key domain primitive โ€” it holds a date plus start/end time, and its overlapsWith() method is the foundation of conflict detection. Two slots conflict if they share the same date and their time ranges intersect.

import java.time.*;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

// --- Enums ---

enum TableType { INDOOR, OUTDOOR, VIP }

enum BookingStatus { CONFIRMED, SEATED, COMPLETED, CANCELLED }

// --- TimeSlot ---

class TimeSlot {
    private final LocalDate date;
    private final LocalTime startTime;
    private final LocalTime endTime;

    public TimeSlot(LocalDate date, LocalTime startTime, LocalTime endTime) {
        this.date = date;
        this.startTime = startTime;
        this.endTime = endTime;
    }

    public boolean overlapsWith(TimeSlot other) {
        if (!this.date.equals(other.date)) return false;
        return this.startTime.isBefore(other.endTime) && other.startTime.isBefore(this.endTime);
    }

    public LocalDate getDate() { return date; }
    public LocalTime getStartTime() { return startTime; }
    public LocalTime getEndTime() { return endTime; }

    @Override
    public String toString() {
        return date + " " + startTime + "-" + endTime;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof TimeSlot)) return false;
        TimeSlot t = (TimeSlot) o;
        return date.equals(t.date) && startTime.equals(t.startTime) && endTime.equals(t.endTime);
    }

    @Override
    public int hashCode() { return Objects.hash(date, startTime, endTime); }
}
import threading
from datetime import date, time, datetime
from enum import Enum
from dataclasses import dataclass, field
from typing import List, Optional, Protocol


# --- Enums ---

class TableType(Enum):
    INDOOR = "INDOOR"
    OUTDOOR = "OUTDOOR"
    VIP = "VIP"


class BookingStatus(Enum):
    CONFIRMED = "CONFIRMED"
    SEATED = "SEATED"
    COMPLETED = "COMPLETED"
    CANCELLED = "CANCELLED"


# --- TimeSlot ---

@dataclass
class TimeSlot:
    date: date
    start_time: time
    end_time: time

    def overlaps_with(self, other: "TimeSlot") -> bool:
        if self.date != other.date:
            return False
        return self.start_time < other.end_time and other.start_time < self.end_time

    def __hash__(self):
        return hash((self.date, self.start_time, self.end_time))

    def __eq__(self, other):
        if not isinstance(other, TimeSlot):
            return False
        return (self.date == other.date and self.start_time == other.start_time
                and self.end_time == other.end_time)

    def __str__(self):
        return f"{self.date} {self.start_time}-{self.end_time}"
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <list>
#include <algorithm>
#include <mutex>
#include <memory>
#include <stdexcept>
#include <sstream>
#include <chrono>

// --- Enums ---

enum class TableType { INDOOR, OUTDOOR, VIP };
enum class BookingStatus { CONFIRMED, SEATED, COMPLETED, CANCELLED };

std::string tableTypeStr(TableType t) {
    switch (t) {
        case TableType::INDOOR: return "INDOOR";
        case TableType::OUTDOOR: return "OUTDOOR";
        case TableType::VIP: return "VIP";
    }
    return "";
}

std::string bookingStatusStr(BookingStatus s) {
    switch (s) {
        case BookingStatus::CONFIRMED: return "CONFIRMED";
        case BookingStatus::SEATED: return "SEATED";
        case BookingStatus::COMPLETED: return "COMPLETED";
        case BookingStatus::CANCELLED: return "CANCELLED";
    }
    return "";
}

// --- TimeSlot ---

struct TimeSlot {
    int year, month, day;
    int startHour, startMin;
    int endHour, endMin;

    bool overlapsWith(const TimeSlot& other) const {
        if (year != other.year || month != other.month || day != other.day) return false;
        int thisStart = startHour * 60 + startMin;
        int thisEnd = endHour * 60 + endMin;
        int otherStart = other.startHour * 60 + other.startMin;
        int otherEnd = other.endHour * 60 + other.endMin;
        return thisStart < otherEnd && otherStart < thisEnd;
    }

    bool operator==(const TimeSlot& other) const {
        return year == other.year && month == other.month && day == other.day
            && startHour == other.startHour && startMin == other.startMin
            && endHour == other.endHour && endMin == other.endMin;
    }

    bool operator<(const TimeSlot& other) const {
        if (year != other.year) return year < other.year;
        if (month != other.month) return month < other.month;
        if (day != other.day) return day < other.day;
        if (startHour != other.startHour) return startHour < other.startHour;
        return startMin < other.startMin;
    }

    std::string toString() const {
        std::ostringstream oss;
        oss << year << "-" << month << "-" << day << " "
            << startHour << ":" << (startMin < 10 ? "0" : "") << startMin
            << "-" << endHour << ":" << (endMin < 10 ? "0" : "") << endMin;
        return oss.str();
    }
};
// --- Restaurant Booking System (JavaScript) ---

// --- Enums ---

const TableType = Object.freeze({ INDOOR: "INDOOR", OUTDOOR: "OUTDOOR", VIP: "VIP" });
const BookingStatus = Object.freeze({
    CONFIRMED: "CONFIRMED", SEATED: "SEATED", COMPLETED: "COMPLETED", CANCELLED: "CANCELLED"
});

// --- TimeSlot ---

class TimeSlot {
    constructor(date, startTime, endTime) {
        this.date = date;         // "YYYY-MM-DD"
        this.startTime = startTime; // "HH:MM"
        this.endTime = endTime;     // "HH:MM"
    }

    overlapsWith(other) {
        if (this.date !== other.date) return false;
        return this.startTime < other.endTime && other.startTime < this.endTime;
    }

    key() { return `${this.date}_${this.startTime}_${this.endTime}`; }

    toString() { return `${this.date} ${this.startTime}-${this.endTime}`; }
}

Table and Booking

Table is a simple immutable entity โ€” id, capacity, and type. Booking is where the state machine lives. Each booking starts in CONFIRMED and can only transition forward: CONFIRMED โ†’ SEATED โ†’ COMPLETED, or CONFIRMED โ†’ CANCELLED. Any invalid transition throws an exception.

๐Ÿ’ก State integrity: the Booking class enforces valid transitions internally rather than relying on the service layer to check. This means even if a bug in BookingService calls complete() on a CONFIRMED booking, it will be caught immediately. Defense in depth.

class Table {
    private final String id;
    private final int capacity;
    private final TableType tableType;

    public Table(String id, int capacity, TableType tableType) {
        this.id = id;
        this.capacity = capacity;
        this.tableType = tableType;
    }

    public String getId() { return id; }
    public int getCapacity() { return capacity; }
    public TableType getTableType() { return tableType; }

    @Override
    public String toString() {
        return "Table-" + id + "(" + tableType + ", seats:" + capacity + ")";
    }
}

class Booking {
    private final String id;
    private final String customerName;
    private final int partySize;
    private final Table table;
    private final TimeSlot timeSlot;
    private BookingStatus status;

    public Booking(String id, String customerName, int partySize, Table table, TimeSlot timeSlot) {
        this.id = id;
        this.customerName = customerName;
        this.partySize = partySize;
        this.table = table;
        this.timeSlot = timeSlot;
        this.status = BookingStatus.CONFIRMED;
    }

    public void seat() {
        if (status != BookingStatus.CONFIRMED)
            throw new IllegalStateException("Can only seat from CONFIRMED state. Current: " + status);
        this.status = BookingStatus.SEATED;
    }

    public void complete() {
        if (status != BookingStatus.SEATED)
            throw new IllegalStateException("Can only complete from SEATED state. Current: " + status);
        this.status = BookingStatus.COMPLETED;
    }

    public void cancel() {
        if (status != BookingStatus.CONFIRMED)
            throw new IllegalStateException("Can only cancel from CONFIRMED state. Current: " + status);
        this.status = BookingStatus.CANCELLED;
    }

    public String getId() { return id; }
    public String getCustomerName() { return customerName; }
    public int getPartySize() { return partySize; }
    public Table getTable() { return table; }
    public TimeSlot getTimeSlot() { return timeSlot; }
    public BookingStatus getStatus() { return status; }

    @Override
    public String toString() {
        return "Booking[" + id + "] " + customerName + " party:" + partySize +
               " " + table + " " + timeSlot + " status:" + status;
    }
}
@dataclass
class Table:
    id: str
    capacity: int
    table_type: TableType

    def __str__(self):
        return f"Table-{self.id}({self.table_type.value}, seats:{self.capacity})"


@dataclass
class Booking:
    id: str
    customer_name: str
    party_size: int
    table: Table
    time_slot: TimeSlot
    status: BookingStatus = BookingStatus.CONFIRMED

    def seat(self):
        if self.status != BookingStatus.CONFIRMED:
            raise ValueError(f"Can only seat from CONFIRMED. Current: {self.status.value}")
        self.status = BookingStatus.SEATED

    def complete(self):
        if self.status != BookingStatus.SEATED:
            raise ValueError(f"Can only complete from SEATED. Current: {self.status.value}")
        self.status = BookingStatus.COMPLETED

    def cancel(self):
        if self.status != BookingStatus.CONFIRMED:
            raise ValueError(f"Can only cancel from CONFIRMED. Current: {self.status.value}")
        self.status = BookingStatus.CANCELLED

    def __str__(self):
        return (f"Booking[{self.id}] {self.customer_name} party:{self.party_size} "
                f"{self.table} {self.time_slot} status:{self.status.value}")
struct Table {
    std::string id;
    int capacity;
    TableType tableType;

    std::string toString() const {
        return "Table-" + id + "(" + tableTypeStr(tableType) + ", seats:" + std::to_string(capacity) + ")";
    }
};

struct Booking {
    std::string id;
    std::string customerName;
    int partySize;
    std::shared_ptr<Table> table;
    TimeSlot timeSlot;
    BookingStatus status = BookingStatus::CONFIRMED;

    void seat() {
        if (status != BookingStatus::CONFIRMED)
            throw std::runtime_error("Can only seat from CONFIRMED. Current: " + bookingStatusStr(status));
        status = BookingStatus::SEATED;
    }

    void complete() {
        if (status != BookingStatus::SEATED)
            throw std::runtime_error("Can only complete from SEATED. Current: " + bookingStatusStr(status));
        status = BookingStatus::COMPLETED;
    }

    void cancel() {
        if (status != BookingStatus::CONFIRMED)
            throw std::runtime_error("Can only cancel from CONFIRMED. Current: " + bookingStatusStr(status));
        status = BookingStatus::CANCELLED;
    }

    std::string toString() const {
        return "Booking[" + id + "] " + customerName + " party:" + std::to_string(partySize)
               + " " + table->toString() + " " + timeSlot.toString() + " status:" + bookingStatusStr(status);
    }
};
class Table {
    constructor(id, capacity, tableType) {
        this.id = id;
        this.capacity = capacity;
        this.tableType = tableType;
    }

    toString() { return `Table-${this.id}(${this.tableType}, seats:${this.capacity})`; }
}

class Booking {
    constructor(id, customerName, partySize, table, timeSlot) {
        this.id = id;
        this.customerName = customerName;
        this.partySize = partySize;
        this.table = table;
        this.timeSlot = timeSlot;
        this.status = BookingStatus.CONFIRMED;
    }

    seat() {
        if (this.status !== BookingStatus.CONFIRMED)
            throw new Error(`Can only seat from CONFIRMED. Current: ${this.status}`);
        this.status = BookingStatus.SEATED;
    }

    complete() {
        if (this.status !== BookingStatus.SEATED)
            throw new Error(`Can only complete from SEATED. Current: ${this.status}`);
        this.status = BookingStatus.COMPLETED;
    }

    cancel() {
        if (this.status !== BookingStatus.CONFIRMED)
            throw new Error(`Can only cancel from CONFIRMED. Current: ${this.status}`);
        this.status = BookingStatus.CANCELLED;
    }

    toString() {
        return `Booking[${this.id}] ${this.customerName} party:${this.partySize} ` +
               `${this.table.toString()} ${this.timeSlot.toString()} status:${this.status}`;
    }
}

WaitlistEntry

A simple value object representing a customer waiting for a table in a specific time slot. Entries are stored in FIFO order per slot โ€” the first person to join the waitlist gets promoted first when a cancellation frees up a table.

class WaitlistEntry {
    private final String customerName;
    private final int partySize;
    private final TimeSlot timeSlot;
    private final LocalDateTime addedAt;

    public WaitlistEntry(String customerName, int partySize, TimeSlot timeSlot) {
        this.customerName = customerName;
        this.partySize = partySize;
        this.timeSlot = timeSlot;
        this.addedAt = LocalDateTime.now();
    }

    public String getCustomerName() { return customerName; }
    public int getPartySize() { return partySize; }
    public TimeSlot getTimeSlot() { return timeSlot; }
    public LocalDateTime getAddedAt() { return addedAt; }

    @Override
    public String toString() {
        return "Waitlist[" + customerName + " party:" + partySize + " slot:" + timeSlot + "]";
    }
}
@dataclass
class WaitlistEntry:
    customer_name: str
    party_size: int
    time_slot: TimeSlot
    added_at: datetime = field(default_factory=datetime.now)

    def __str__(self):
        return f"Waitlist[{self.customer_name} party:{self.party_size} slot:{self.time_slot}]"
struct WaitlistEntry {
    std::string customerName;
    int partySize;
    TimeSlot timeSlot;

    std::string toString() const {
        return "Waitlist[" + customerName + " party:" + std::to_string(partySize)
               + " slot:" + timeSlot.toString() + "]";
    }
};
class WaitlistEntry {
    constructor(customerName, partySize, timeSlot) {
        this.customerName = customerName;
        this.partySize = partySize;
        this.timeSlot = timeSlot;
        this.addedAt = new Date();
    }

    toString() {
        return `Waitlist[${this.customerName} party:${this.partySize} slot:${this.timeSlot.toString()}]`;
    }
}

Table Assignment Strategy

The Strategy interface defines how a table is selected from a list of available candidates. FirstFitStrategy picks the first table with enough capacity โ€” simple and fast. BestFitStrategy picks the smallest table that fits โ€” minimizes wasted seats so larger tables remain available for bigger parties.

๐Ÿ’ก Strategy pattern = define a family of algorithms, encapsulate each one, and make them interchangeable. BookingService delegates table selection to whichever strategy is currently set. You can swap strategies at runtime with setStrategy() โ€” no code changes, no if/else branching.

// --- Strategy Pattern ---

interface TableAssignmentStrategy {
    Table assignTable(List<Table> availableTables, int partySize);
}

class FirstFitStrategy implements TableAssignmentStrategy {
    @Override
    public Table assignTable(List<Table> availableTables, int partySize) {
        return availableTables.stream()
                .filter(t -> t.getCapacity() >= partySize)
                .findFirst()
                .orElse(null);
    }
}

class BestFitStrategy implements TableAssignmentStrategy {
    @Override
    public Table assignTable(List<Table> availableTables, int partySize) {
        return availableTables.stream()
                .filter(t -> t.getCapacity() >= partySize)
                .min(Comparator.comparingInt(Table::getCapacity))
                .orElse(null);
    }
}
# --- Strategy Pattern ---

class TableAssignmentStrategy(Protocol):
    def assign_table(self, available_tables: List[Table], party_size: int) -> Optional[Table]:
        ...


class FirstFitStrategy:
    def assign_table(self, available_tables: List[Table], party_size: int) -> Optional[Table]:
        for table in available_tables:
            if table.capacity >= party_size:
                return table
        return None


class BestFitStrategy:
    def assign_table(self, available_tables: List[Table], party_size: int) -> Optional[Table]:
        fitting = [t for t in available_tables if t.capacity >= party_size]
        if not fitting:
            return None
        return min(fitting, key=lambda t: t.capacity)
// --- Strategy Pattern ---

class TableAssignmentStrategy {
public:
    virtual ~TableAssignmentStrategy() = default;
    virtual std::shared_ptr<Table> assignTable(
        const std::vector<std::shared_ptr<Table>>& available, int partySize) = 0;
};

class FirstFitStrategy : public TableAssignmentStrategy {
public:
    std::shared_ptr<Table> assignTable(
        const std::vector<std::shared_ptr<Table>>& available, int partySize) override {
        for (auto& t : available) {
            if (t->capacity >= partySize) return t;
        }
        return nullptr;
    }
};

class BestFitStrategy : public TableAssignmentStrategy {
public:
    std::shared_ptr<Table> assignTable(
        const std::vector<std::shared_ptr<Table>>& available, int partySize) override {
        std::shared_ptr<Table> best = nullptr;
        for (auto& t : available) {
            if (t->capacity >= partySize) {
                if (!best || t->capacity < best->capacity) {
                    best = t;
                }
            }
        }
        return best;
    }
};
// --- Strategy Pattern ---

class FirstFitStrategy {
    assignTable(availableTables, partySize) {
        return availableTables.find(t => t.capacity >= partySize) || null;
    }
}

class BestFitStrategy {
    assignTable(availableTables, partySize) {
        const fitting = availableTables
            .filter(t => t.capacity >= partySize)
            .sort((a, b) => a.capacity - b.capacity);
        return fitting.length > 0 ? fitting[0] : null;
    }
}

Repositories

Storage is abstracted behind repository interfaces. The InMemory implementations use simple maps and lists โ€” but the same interface could be backed by PostgreSQL, DynamoDB, or any persistent store without changing a single line in BookingService.

// --- Repositories ---

interface TableRepository {
    void save(Table table);
    Table findById(String id);
    List<Table> findAll();
}

interface BookingRepository {
    void save(Booking booking);
    Booking findById(String id);
    List<Booking> findByTableAndOverlappingSlot(String tableId, TimeSlot slot);
    List<Booking> findAll();
}

interface WaitlistRepository {
    void add(WaitlistEntry entry);
    List<WaitlistEntry> getBySlot(TimeSlot slot);
    WaitlistEntry removeFirst(TimeSlot slot);
}

class InMemoryTableRepository implements TableRepository {
    private final Map<String, Table> tables = new HashMap<>();

    @Override
    public void save(Table table) { tables.put(table.getId(), table); }

    @Override
    public Table findById(String id) { return tables.get(id); }

    @Override
    public List<Table> findAll() { return new ArrayList<>(tables.values()); }
}

class InMemoryBookingRepository implements BookingRepository {
    private final List<Booking> bookings = new ArrayList<>();

    @Override
    public void save(Booking booking) { bookings.add(booking); }

    @Override
    public Booking findById(String id) {
        return bookings.stream().filter(b -> b.getId().equals(id)).findFirst().orElse(null);
    }

    @Override
    public List<Booking> findByTableAndOverlappingSlot(String tableId, TimeSlot slot) {
        return bookings.stream()
                .filter(b -> b.getTable().getId().equals(tableId))
                .filter(b -> b.getStatus() == BookingStatus.CONFIRMED || b.getStatus() == BookingStatus.SEATED)
                .filter(b -> b.getTimeSlot().overlapsWith(slot))
                .collect(Collectors.toList());
    }

    @Override
    public List<Booking> findAll() { return new ArrayList<>(bookings); }
}

class InMemoryWaitlistRepository implements WaitlistRepository {
    private final Map<TimeSlot, LinkedList<WaitlistEntry>> waitlist = new HashMap<>();

    @Override
    public void add(WaitlistEntry entry) {
        waitlist.computeIfAbsent(entry.getTimeSlot(), k -> new LinkedList<>()).add(entry);
    }

    @Override
    public List<WaitlistEntry> getBySlot(TimeSlot slot) {
        return waitlist.getOrDefault(slot, new LinkedList<>());
    }

    @Override
    public WaitlistEntry removeFirst(TimeSlot slot) {
        LinkedList<WaitlistEntry> list = waitlist.get(slot);
        if (list == null || list.isEmpty()) return null;
        return list.removeFirst();
    }
}
# --- Repositories ---

class InMemoryTableRepository:
    def __init__(self):
        self._tables: dict[str, Table] = {}

    def save(self, table: Table):
        self._tables[table.id] = table

    def find_by_id(self, table_id: str) -> Optional[Table]:
        return self._tables.get(table_id)

    def find_all(self) -> List[Table]:
        return list(self._tables.values())


class InMemoryBookingRepository:
    def __init__(self):
        self._bookings: List[Booking] = []

    def save(self, booking: Booking):
        self._bookings.append(booking)

    def find_by_id(self, booking_id: str) -> Optional[Booking]:
        for b in self._bookings:
            if b.id == booking_id:
                return b
        return None

    def find_by_table_and_overlapping_slot(self, table_id: str, slot: TimeSlot) -> List[Booking]:
        return [
            b for b in self._bookings
            if b.table.id == table_id
            and b.status in (BookingStatus.CONFIRMED, BookingStatus.SEATED)
            and b.time_slot.overlaps_with(slot)
        ]


class InMemoryWaitlistRepository:
    def __init__(self):
        self._waitlist: dict[TimeSlot, list[WaitlistEntry]] = {}

    def add(self, entry: WaitlistEntry):
        self._waitlist.setdefault(entry.time_slot, []).append(entry)

    def get_by_slot(self, slot: TimeSlot) -> List[WaitlistEntry]:
        return self._waitlist.get(slot, [])

    def remove_first(self, slot: TimeSlot) -> Optional[WaitlistEntry]:
        entries = self._waitlist.get(slot, [])
        if not entries:
            return None
        return entries.pop(0)
// Note: In C++, the BookingService class below acts as its own repository
// using internal vectors and maps. No separate repository classes needed
// for this in-memory implementation.
// Note: In JavaScript, the BookingService class below acts as its own repository
// using internal arrays and Maps. No separate repository classes needed
// for this in-memory implementation.

BookingService

The main orchestrator. It holds references to all repositories, the current assignment strategy, and a lock for thread safety. The bookTable() method is the heart of the system โ€” it acquires the lock, finds available tables, delegates to the strategy, and either confirms a booking or adds the customer to the waitlist.

๐Ÿ’ก Auto-promotion: when cancelBooking() is called, it doesnโ€™t just cancel โ€” it immediately calls promoteFromWaitlist() to try seating the next person in the FIFO queue. This is the Observer pattern in action: cancellation implicitly โ€œnotifiesโ€ the waitlist. The customer who was first in line gets automatically promoted without any manual intervention.

// --- Booking Result ---

class BookingResult {
    private final Booking booking;
    private final boolean waitlisted;
    private final WaitlistEntry waitlistEntry;

    private BookingResult(Booking booking, boolean waitlisted, WaitlistEntry waitlistEntry) {
        this.booking = booking;
        this.waitlisted = waitlisted;
        this.waitlistEntry = waitlistEntry;
    }

    public static BookingResult confirmed(Booking booking) {
        return new BookingResult(booking, false, null);
    }

    public static BookingResult waitlisted(WaitlistEntry entry) {
        return new BookingResult(null, true, entry);
    }

    public Booking getBooking() { return booking; }
    public boolean isWaitlisted() { return waitlisted; }
    public WaitlistEntry getWaitlistEntry() { return waitlistEntry; }

    @Override
    public String toString() {
        if (waitlisted) return "WAITLISTED: " + waitlistEntry;
        return "CONFIRMED: " + booking;
    }
}

// --- Booking Service ---

class BookingService {
    private final TableRepository tableRepo;
    private final BookingRepository bookingRepo;
    private final WaitlistRepository waitlistRepo;
    private TableAssignmentStrategy strategy;
    private final ReentrantLock lock = new ReentrantLock();
    private int bookingCounter = 0;
    private int tableCounter = 0;

    public BookingService(TableAssignmentStrategy strategy) {
        this.tableRepo = new InMemoryTableRepository();
        this.bookingRepo = new InMemoryBookingRepository();
        this.waitlistRepo = new InMemoryWaitlistRepository();
        this.strategy = strategy;
    }

    public void setStrategy(TableAssignmentStrategy strategy) {
        this.strategy = strategy;
    }

    public Table addTable(int capacity, TableType type) {
        Table table = new Table("T" + (++tableCounter), capacity, type);
        tableRepo.save(table);
        return table;
    }

    public List<Table> getAvailableTables(TimeSlot slot, int partySize) {
        return tableRepo.findAll().stream()
                .filter(t -> t.getCapacity() >= partySize)
                .filter(t -> bookingRepo.findByTableAndOverlappingSlot(t.getId(), slot).isEmpty())
                .collect(Collectors.toList());
    }

    public BookingResult bookTable(String customerName, int partySize, TimeSlot slot) {
        lock.lock();
        try {
            List<Table> available = getAvailableTables(slot, partySize);
            Table assigned = strategy.assignTable(available, partySize);

            if (assigned != null) {
                Booking booking = new Booking("B" + (++bookingCounter), customerName,
                        partySize, assigned, slot);
                bookingRepo.save(booking);
                System.out.println("  >> Booked: " + booking);
                return BookingResult.confirmed(booking);
            } else {
                WaitlistEntry entry = new WaitlistEntry(customerName, partySize, slot);
                waitlistRepo.add(entry);
                System.out.println("  >> Waitlisted: " + entry);
                return BookingResult.waitlisted(entry);
            }
        } finally {
            lock.unlock();
        }
    }

    public void cancelBooking(String bookingId) {
        lock.lock();
        try {
            Booking booking = bookingRepo.findById(bookingId);
            if (booking == null) throw new IllegalArgumentException("Booking not found: " + bookingId);
            booking.cancel();
            System.out.println("  >> Cancelled: " + booking);
            promoteFromWaitlist(booking.getTimeSlot());
        } finally {
            lock.unlock();
        }
    }

    private void promoteFromWaitlist(TimeSlot slot) {
        WaitlistEntry entry = waitlistRepo.removeFirst(slot);
        if (entry == null) return;

        List<Table> available = getAvailableTables(slot, entry.getPartySize());
        Table assigned = strategy.assignTable(available, entry.getPartySize());

        if (assigned != null) {
            Booking booking = new Booking("B" + (++bookingCounter), entry.getCustomerName(),
                    entry.getPartySize(), assigned, slot);
            bookingRepo.save(booking);
            System.out.println("  >> Promoted from waitlist: " + booking);
        } else {
            waitlistRepo.add(entry);
        }
    }

    public void seatGuests(String bookingId) {
        Booking booking = bookingRepo.findById(bookingId);
        if (booking == null) throw new IllegalArgumentException("Booking not found: " + bookingId);
        booking.seat();
        System.out.println("  >> Seated: " + booking);
    }

    public void completeBooking(String bookingId) {
        Booking booking = bookingRepo.findById(bookingId);
        if (booking == null) throw new IllegalArgumentException("Booking not found: " + bookingId);
        booking.complete();
        System.out.println("  >> Completed: " + booking);
    }

    public List<WaitlistEntry> getWaitlist(TimeSlot slot) {
        return waitlistRepo.getBySlot(slot);
    }
}
# --- Booking Result ---

@dataclass
class BookingResult:
    booking: Optional[Booking] = None
    waitlisted: bool = False
    waitlist_entry: Optional[WaitlistEntry] = None

    def __str__(self):
        if self.waitlisted:
            return f"WAITLISTED: {self.waitlist_entry}"
        return f"CONFIRMED: {self.booking}"


# --- Booking Service ---

class BookingService:
    def __init__(self, strategy: TableAssignmentStrategy):
        self._table_repo = InMemoryTableRepository()
        self._booking_repo = InMemoryBookingRepository()
        self._waitlist_repo = InMemoryWaitlistRepository()
        self._strategy = strategy
        self._lock = threading.Lock()
        self._booking_counter = 0
        self._table_counter = 0

    def set_strategy(self, strategy: TableAssignmentStrategy):
        self._strategy = strategy

    def add_table(self, capacity: int, table_type: TableType) -> Table:
        self._table_counter += 1
        table = Table(f"T{self._table_counter}", capacity, table_type)
        self._table_repo.save(table)
        return table

    def get_available_tables(self, slot: TimeSlot, party_size: int) -> List[Table]:
        return [
            t for t in self._table_repo.find_all()
            if t.capacity >= party_size
            and not self._booking_repo.find_by_table_and_overlapping_slot(t.id, slot)
        ]

    def book_table(self, customer_name: str, party_size: int, slot: TimeSlot) -> BookingResult:
        with self._lock:
            available = self.get_available_tables(slot, party_size)
            assigned = self._strategy.assign_table(available, party_size)

            if assigned:
                self._booking_counter += 1
                booking = Booking(f"B{self._booking_counter}", customer_name,
                                  party_size, assigned, slot)
                self._booking_repo.save(booking)
                print(f"  >> Booked: {booking}")
                return BookingResult(booking=booking)
            else:
                entry = WaitlistEntry(customer_name, party_size, slot)
                self._waitlist_repo.add(entry)
                print(f"  >> Waitlisted: {entry}")
                return BookingResult(waitlisted=True, waitlist_entry=entry)

    def cancel_booking(self, booking_id: str):
        with self._lock:
            booking = self._booking_repo.find_by_id(booking_id)
            if not booking:
                raise ValueError(f"Booking not found: {booking_id}")
            booking.cancel()
            print(f"  >> Cancelled: {booking}")
            self._promote_from_waitlist(booking.time_slot)

    def _promote_from_waitlist(self, slot: TimeSlot):
        entry = self._waitlist_repo.remove_first(slot)
        if not entry:
            return

        available = self.get_available_tables(slot, entry.party_size)
        assigned = self._strategy.assign_table(available, entry.party_size)

        if assigned:
            self._booking_counter += 1
            booking = Booking(f"B{self._booking_counter}", entry.customer_name,
                              entry.party_size, assigned, slot)
            self._booking_repo.save(booking)
            print(f"  >> Promoted from waitlist: {booking}")
        else:
            self._waitlist_repo.add(entry)

    def seat_guests(self, booking_id: str):
        booking = self._booking_repo.find_by_id(booking_id)
        if not booking:
            raise ValueError(f"Booking not found: {booking_id}")
        booking.seat()
        print(f"  >> Seated: {booking}")

    def complete_booking(self, booking_id: str):
        booking = self._booking_repo.find_by_id(booking_id)
        if not booking:
            raise ValueError(f"Booking not found: {booking_id}")
        booking.complete()
        print(f"  >> Completed: {booking}")

    def get_waitlist(self, slot: TimeSlot) -> List[WaitlistEntry]:
        return self._waitlist_repo.get_by_slot(slot)
// --- Booking Result ---

struct BookingResult {
    std::shared_ptr<Booking> booking;
    bool waitlisted = false;
    std::shared_ptr<WaitlistEntry> waitlistEntry;

    std::string toString() const {
        if (waitlisted) return "WAITLISTED: " + waitlistEntry->toString();
        return "CONFIRMED: " + booking->toString();
    }
};

// --- Booking Service ---

class BookingService {
    std::vector<std::shared_ptr<Table>> tables;
    std::vector<std::shared_ptr<Booking>> bookings;
    std::map<TimeSlot, std::list<std::shared_ptr<WaitlistEntry>>> waitlist;
    std::shared_ptr<TableAssignmentStrategy> strategy;
    std::mutex mtx;
    int bookingCounter = 0;
    int tableCounter = 0;

public:
    BookingService(std::shared_ptr<TableAssignmentStrategy> strat) : strategy(strat) {}

    void setStrategy(std::shared_ptr<TableAssignmentStrategy> strat) {
        strategy = strat;
    }

    std::shared_ptr<Table> addTable(int capacity, TableType type) {
        auto table = std::make_shared<Table>();
        table->id = "T" + std::to_string(++tableCounter);
        table->capacity = capacity;
        table->tableType = type;
        tables.push_back(table);
        return table;
    }

    std::vector<std::shared_ptr<Table>> getAvailableTables(const TimeSlot& slot, int partySize) {
        std::vector<std::shared_ptr<Table>> available;
        for (auto& t : tables) {
            if (t->capacity < partySize) continue;
            bool conflict = false;
            for (auto& b : bookings) {
                if (b->table->id == t->id
                    && (b->status == BookingStatus::CONFIRMED || b->status == BookingStatus::SEATED)
                    && b->timeSlot.overlapsWith(slot)) {
                    conflict = true;
                    break;
                }
            }
            if (!conflict) available.push_back(t);
        }
        return available;
    }

    BookingResult bookTable(const std::string& customerName, int partySize, const TimeSlot& slot) {
        std::lock_guard<std::mutex> lock(mtx);
        auto available = getAvailableTables(slot, partySize);
        auto assigned = strategy->assignTable(available, partySize);

        if (assigned) {
            auto booking = std::make_shared<Booking>();
            booking->id = "B" + std::to_string(++bookingCounter);
            booking->customerName = customerName;
            booking->partySize = partySize;
            booking->table = assigned;
            booking->timeSlot = slot;
            bookings.push_back(booking);
            std::cout << "  >> Booked: " << booking->toString() << std::endl;
            return {booking, false, nullptr};
        } else {
            auto entry = std::make_shared<WaitlistEntry>();
            entry->customerName = customerName;
            entry->partySize = partySize;
            entry->timeSlot = slot;
            waitlist[slot].push_back(entry);
            std::cout << "  >> Waitlisted: " << entry->toString() << std::endl;
            return {nullptr, true, entry};
        }
    }

    void cancelBooking(const std::string& bookingId) {
        std::lock_guard<std::mutex> lock(mtx);
        auto booking = findBooking(bookingId);
        if (!booking) throw std::runtime_error("Booking not found: " + bookingId);
        booking->cancel();
        std::cout << "  >> Cancelled: " << booking->toString() << std::endl;
        promoteFromWaitlist(booking->timeSlot);
    }

    void seatGuests(const std::string& bookingId) {
        auto booking = findBooking(bookingId);
        if (!booking) throw std::runtime_error("Booking not found: " + bookingId);
        booking->seat();
        std::cout << "  >> Seated: " << booking->toString() << std::endl;
    }

    void completeBooking(const std::string& bookingId) {
        auto booking = findBooking(bookingId);
        if (!booking) throw std::runtime_error("Booking not found: " + bookingId);
        booking->complete();
        std::cout << "  >> Completed: " << booking->toString() << std::endl;
    }

    std::list<std::shared_ptr<WaitlistEntry>> getWaitlist(const TimeSlot& slot) {
        return waitlist[slot];
    }

private:
    std::shared_ptr<Booking> findBooking(const std::string& id) {
        for (auto& b : bookings) {
            if (b->id == id) return b;
        }
        return nullptr;
    }

    void promoteFromWaitlist(const TimeSlot& slot) {
        auto& list = waitlist[slot];
        if (list.empty()) return;

        auto entry = list.front();
        list.pop_front();

        auto available = getAvailableTables(slot, entry->partySize);
        auto assigned = strategy->assignTable(available, entry->partySize);

        if (assigned) {
            auto booking = std::make_shared<Booking>();
            booking->id = "B" + std::to_string(++bookingCounter);
            booking->customerName = entry->customerName;
            booking->partySize = entry->partySize;
            booking->table = assigned;
            booking->timeSlot = slot;
            bookings.push_back(booking);
            std::cout << "  >> Promoted from waitlist: " << booking->toString() << std::endl;
        } else {
            list.push_back(entry);
        }
    }
};
// --- Async Lock (single-threaded safety for async contexts) ---

class AsyncLock {
    constructor() { this._queue = []; this._locked = false; }

    async acquire() {
        if (!this._locked) {
            this._locked = true;
            return;
        }
        return new Promise(resolve => this._queue.push(resolve));
    }

    release() {
        if (this._queue.length > 0) {
            const next = this._queue.shift();
            next();
        } else {
            this._locked = false;
        }
    }
}

// --- Booking Service ---

class BookingService {
    constructor(strategy) {
        this.tables = [];
        this.bookings = [];
        this.waitlist = new Map(); // key(TimeSlot) -> WaitlistEntry[]
        this.strategy = strategy;
        this.lock = new AsyncLock();
        this.bookingCounter = 0;
        this.tableCounter = 0;
    }

    setStrategy(strategy) { this.strategy = strategy; }

    addTable(capacity, tableType) {
        const table = new Table(`T${++this.tableCounter}`, capacity, tableType);
        this.tables.push(table);
        return table;
    }

    getAvailableTables(slot, partySize) {
        return this.tables.filter(t => {
            if (t.capacity < partySize) return false;
            const hasConflict = this.bookings.some(b =>
                b.table.id === t.id &&
                (b.status === BookingStatus.CONFIRMED || b.status === BookingStatus.SEATED) &&
                b.timeSlot.overlapsWith(slot)
            );
            return !hasConflict;
        });
    }

    async bookTable(customerName, partySize, slot) {
        await this.lock.acquire();
        try {
            const available = this.getAvailableTables(slot, partySize);
            const assigned = this.strategy.assignTable(available, partySize);

            if (assigned) {
                const booking = new Booking(
                    `B${++this.bookingCounter}`, customerName, partySize, assigned, slot
                );
                this.bookings.push(booking);
                console.log(`  >> Booked: ${booking.toString()}`);
                return { booking, waitlisted: false };
            } else {
                const entry = new WaitlistEntry(customerName, partySize, slot);
                const key = slot.key();
                if (!this.waitlist.has(key)) this.waitlist.set(key, []);
                this.waitlist.get(key).push(entry);
                console.log(`  >> Waitlisted: ${entry.toString()}`);
                return { booking: null, waitlisted: true, waitlistEntry: entry };
            }
        } finally {
            this.lock.release();
        }
    }

    async cancelBooking(bookingId) {
        await this.lock.acquire();
        try {
            const booking = this.bookings.find(b => b.id === bookingId);
            if (!booking) throw new Error(`Booking not found: ${bookingId}`);
            booking.cancel();
            console.log(`  >> Cancelled: ${booking.toString()}`);
            this._promoteFromWaitlist(booking.timeSlot);
        } finally {
            this.lock.release();
        }
    }

    _promoteFromWaitlist(slot) {
        const key = slot.key();
        const entries = this.waitlist.get(key);
        if (!entries || entries.length === 0) return;

        const entry = entries.shift();
        const available = this.getAvailableTables(slot, entry.partySize);
        const assigned = this.strategy.assignTable(available, entry.partySize);

        if (assigned) {
            const booking = new Booking(
                `B${++this.bookingCounter}`, entry.customerName, entry.partySize, assigned, slot
            );
            this.bookings.push(booking);
            console.log(`  >> Promoted from waitlist: ${booking.toString()}`);
        } else {
            entries.unshift(entry);
        }
    }

    seatGuests(bookingId) {
        const booking = this.bookings.find(b => b.id === bookingId);
        if (!booking) throw new Error(`Booking not found: ${bookingId}`);
        booking.seat();
        console.log(`  >> Seated: ${booking.toString()}`);
    }

    completeBooking(bookingId) {
        const booking = this.bookings.find(b => b.id === bookingId);
        if (!booking) throw new Error(`Booking not found: ${bookingId}`);
        booking.complete();
        console.log(`  >> Completed: ${booking.toString()}`);
    }

    getWaitlist(slot) {
        return this.waitlist.get(slot.key()) || [];
    }
}

Main / Demo

A complete runnable demo showing the system in action: adding tables, booking with BestFit strategy, conflict detection on overlapping slots, waitlist promotion on cancellation, state transitions, and runtime strategy swap to FirstFit.

public class RestaurantBookingSystem {
    public static void main(String[] args) {
        System.out.println("=== Restaurant Booking System Demo ===\n");

        // Create service with BestFit strategy
        BookingService service = new BookingService(new BestFitStrategy());

        // Add tables
        System.out.println("--- Adding Tables ---");
        Table t1 = service.addTable(2, TableType.INDOOR);
        Table t2 = service.addTable(4, TableType.INDOOR);
        Table t3 = service.addTable(6, TableType.OUTDOOR);
        Table t4 = service.addTable(8, TableType.VIP);
        System.out.println("Added: " + t1 + ", " + t2 + ", " + t3 + ", " + t4);

        // Define time slots
        TimeSlot fridayDinner = new TimeSlot(LocalDate.of(2025, 1, 10),
                LocalTime.of(19, 0), LocalTime.of(21, 0));
        TimeSlot fridayLate = new TimeSlot(LocalDate.of(2025, 1, 10),
                LocalTime.of(20, 0), LocalTime.of(22, 0));

        // Book tables - BestFit assigns smallest fitting table
        System.out.println("\n--- Booking with BestFit Strategy ---");
        BookingResult r1 = service.bookTable("Alice", 2, fridayDinner);
        BookingResult r2 = service.bookTable("Bob", 4, fridayDinner);
        BookingResult r3 = service.bookTable("Charlie", 5, fridayDinner);
        BookingResult r4 = service.bookTable("Diana", 7, fridayDinner);

        // This should go to waitlist (all tables booked for overlapping slot)
        System.out.println("\n--- Conflict Detection (overlapping slot) ---");
        BookingResult r5 = service.bookTable("Eve", 2, fridayLate);

        // Show waitlist
        System.out.println("\n--- Waitlist for Friday Late ---");
        service.getWaitlist(fridayLate).forEach(e -> System.out.println("  " + e));

        // Cancel Alice's booking - should auto-promote Eve
        System.out.println("\n--- Cancel Alice (auto-promotes waitlist) ---");
        service.cancelBooking(r1.getBooking().getId());

        // Demonstrate state transitions
        System.out.println("\n--- State Transitions ---");
        service.seatGuests(r2.getBooking().getId());
        service.completeBooking(r2.getBooking().getId());

        // Switch to FirstFit strategy
        System.out.println("\n--- Switch to FirstFit Strategy ---");
        service.setStrategy(new FirstFitStrategy());
        TimeSlot saturdayDinner = new TimeSlot(LocalDate.of(2025, 1, 11),
                LocalTime.of(19, 0), LocalTime.of(21, 0));
        BookingResult r6 = service.bookTable("Frank", 2, saturdayDinner);

        // Show available tables
        System.out.println("\n--- Available tables for Saturday Dinner (party of 2) ---");
        service.getAvailableTables(saturdayDinner, 2)
               .forEach(t -> System.out.println("  " + t));

        System.out.println("\n=== Demo Complete ===");
    }
}
def main():
    print("=== Restaurant Booking System Demo ===\n")

    # Create service with BestFit strategy
    service = BookingService(BestFitStrategy())

    # Add tables
    print("--- Adding Tables ---")
    t1 = service.add_table(2, TableType.INDOOR)
    t2 = service.add_table(4, TableType.INDOOR)
    t3 = service.add_table(6, TableType.OUTDOOR)
    t4 = service.add_table(8, TableType.VIP)
    print(f"Added: {t1}, {t2}, {t3}, {t4}")

    # Define time slots
    friday_dinner = TimeSlot(date(2025, 1, 10), time(19, 0), time(21, 0))
    friday_late = TimeSlot(date(2025, 1, 10), time(20, 0), time(22, 0))

    # Book tables - BestFit assigns smallest fitting table
    print("\n--- Booking with BestFit Strategy ---")
    r1 = service.book_table("Alice", 2, friday_dinner)
    r2 = service.book_table("Bob", 4, friday_dinner)
    r3 = service.book_table("Charlie", 5, friday_dinner)
    r4 = service.book_table("Diana", 7, friday_dinner)

    # This should go to waitlist (all tables booked for overlapping slot)
    print("\n--- Conflict Detection (overlapping slot) ---")
    r5 = service.book_table("Eve", 2, friday_late)

    # Show waitlist
    print("\n--- Waitlist for Friday Late ---")
    for entry in service.get_waitlist(friday_late):
        print(f"  {entry}")

    # Cancel Alice's booking - should auto-promote Eve
    print("\n--- Cancel Alice (auto-promotes waitlist) ---")
    service.cancel_booking(r1.booking.id)

    # Demonstrate state transitions
    print("\n--- State Transitions ---")
    service.seat_guests(r2.booking.id)
    service.complete_booking(r2.booking.id)

    # Switch to FirstFit strategy
    print("\n--- Switch to FirstFit Strategy ---")
    service.set_strategy(FirstFitStrategy())
    saturday_dinner = TimeSlot(date(2025, 1, 11), time(19, 0), time(21, 0))
    r6 = service.book_table("Frank", 2, saturday_dinner)

    # Show available tables
    print("\n--- Available tables for Saturday Dinner (party of 2) ---")
    for t in service.get_available_tables(saturday_dinner, 2):
        print(f"  {t}")

    print("\n=== Demo Complete ===")


if __name__ == "__main__":
    main()
int main() {
    std::cout << "=== Restaurant Booking System Demo ===\n" << std::endl;

    auto service = std::make_unique<BookingService>(std::make_shared<BestFitStrategy>());

    // Add tables
    std::cout << "--- Adding Tables ---" << std::endl;
    auto t1 = service->addTable(2, TableType::INDOOR);
    auto t2 = service->addTable(4, TableType::INDOOR);
    auto t3 = service->addTable(6, TableType::OUTDOOR);
    auto t4 = service->addTable(8, TableType::VIP);
    std::cout << "Added: " << t1->toString() << ", " << t2->toString()
              << ", " << t3->toString() << ", " << t4->toString() << std::endl;

    // Define time slots
    TimeSlot fridayDinner = {2025, 1, 10, 19, 0, 21, 0};
    TimeSlot fridayLate = {2025, 1, 10, 20, 0, 22, 0};

    // Book tables - BestFit assigns smallest fitting table
    std::cout << "\n--- Booking with BestFit Strategy ---" << std::endl;
    auto r1 = service->bookTable("Alice", 2, fridayDinner);
    auto r2 = service->bookTable("Bob", 4, fridayDinner);
    auto r3 = service->bookTable("Charlie", 5, fridayDinner);
    auto r4 = service->bookTable("Diana", 7, fridayDinner);

    // This should go to waitlist
    std::cout << "\n--- Conflict Detection (overlapping slot) ---" << std::endl;
    auto r5 = service->bookTable("Eve", 2, fridayLate);

    // Show waitlist
    std::cout << "\n--- Waitlist for Friday Late ---" << std::endl;
    for (auto& e : service->getWaitlist(fridayLate)) {
        std::cout << "  " << e->toString() << std::endl;
    }

    // Cancel Alice - should auto-promote Eve
    std::cout << "\n--- Cancel Alice (auto-promotes waitlist) ---" << std::endl;
    service->cancelBooking(r1.booking->id);

    // Demonstrate state transitions
    std::cout << "\n--- State Transitions ---" << std::endl;
    service->seatGuests(r2.booking->id);
    service->completeBooking(r2.booking->id);

    // Switch to FirstFit strategy
    std::cout << "\n--- Switch to FirstFit Strategy ---" << std::endl;
    service->setStrategy(std::make_shared<FirstFitStrategy>());
    TimeSlot saturdayDinner = {2025, 1, 11, 19, 0, 21, 0};
    auto r6 = service->bookTable("Frank", 2, saturdayDinner);

    // Show available tables
    std::cout << "\n--- Available tables for Saturday Dinner (party of 2) ---" << std::endl;
    for (auto& t : service->getAvailableTables(saturdayDinner, 2)) {
        std::cout << "  " << t->toString() << std::endl;
    }

    std::cout << "\n=== Demo Complete ===" << std::endl;
    return 0;
}
async function main() {
    console.log("=== Restaurant Booking System Demo ===\n");

    // Create service with BestFit strategy
    const service = new BookingService(new BestFitStrategy());

    // Add tables
    console.log("--- Adding Tables ---");
    const t1 = service.addTable(2, TableType.INDOOR);
    const t2 = service.addTable(4, TableType.INDOOR);
    const t3 = service.addTable(6, TableType.OUTDOOR);
    const t4 = service.addTable(8, TableType.VIP);
    console.log(`Added: ${t1}, ${t2}, ${t3}, ${t4}`);

    // Define time slots
    const fridayDinner = new TimeSlot("2025-01-10", "19:00", "21:00");
    const fridayLate = new TimeSlot("2025-01-10", "20:00", "22:00");

    // Book tables - BestFit assigns smallest fitting table
    console.log("\n--- Booking with BestFit Strategy ---");
    const r1 = await service.bookTable("Alice", 2, fridayDinner);
    const r2 = await service.bookTable("Bob", 4, fridayDinner);
    const r3 = await service.bookTable("Charlie", 5, fridayDinner);
    const r4 = await service.bookTable("Diana", 7, fridayDinner);

    // This should go to waitlist (all tables booked for overlapping slot)
    console.log("\n--- Conflict Detection (overlapping slot) ---");
    const r5 = await service.bookTable("Eve", 2, fridayLate);

    // Show waitlist
    console.log("\n--- Waitlist for Friday Late ---");
    service.getWaitlist(fridayLate).forEach(e => console.log(`  ${e.toString()}`));

    // Cancel Alice's booking - should auto-promote Eve
    console.log("\n--- Cancel Alice (auto-promotes waitlist) ---");
    await service.cancelBooking(r1.booking.id);

    // Demonstrate state transitions
    console.log("\n--- State Transitions ---");
    service.seatGuests(r2.booking.id);
    service.completeBooking(r2.booking.id);

    // Switch to FirstFit strategy
    console.log("\n--- Switch to FirstFit Strategy ---");
    service.setStrategy(new FirstFitStrategy());
    const saturdayDinner = new TimeSlot("2025-01-11", "19:00", "21:00");
    const r6 = await service.bookTable("Frank", 2, saturdayDinner);

    // Show available tables
    console.log("\n--- Available tables for Saturday Dinner (party of 2) ---");
    service.getAvailableTables(saturdayDinner, 2).forEach(t => console.log(`  ${t.toString()}`));

    console.log("\n=== Demo Complete ===");
}

main();

Follow-up Questions

  1. How would you handle recurring reservations (e.g., every Friday at 7pm)? Consider a RecurringBooking entity that generates individual Booking instances for each occurrence. Handle conflicts on specific dates and allow individual cancellations without affecting the series.

  2. How would you implement a no-show policy with automatic cancellation after 15 minutes? A scheduled task checks CONFIRMED bookings past their start time + grace period. Transition to CANCELLED and trigger waitlist promotion. Consider SMS/push notifications before auto-cancel.

  3. How would you scale this for a chain with 100+ restaurants? Shard by restaurant ID. Each restaurantโ€™s bookings are independent, so horizontal partitioning is natural. Use a service registry for routing and consider eventual consistency for cross-restaurant analytics.

  4. How would you add dynamic pricing (premium slots cost more)? Introduce a PricingStrategy interface. Factor in time slot popularity, table type (VIP premium), party size, and day of week. Store pricing rules as configurable metadata per restaurant.

  5. How would you handle table merging for large parties? Add a CompositeTable concept โ€” multiple adjacent tables can be merged. Track which tables are mergeable (adjacency metadata). The assignment strategy must consider both single tables and valid merge combinations.

  6. How would you integrate with a payment system for deposits? Add a PaymentService dependency. On booking confirmation, initiate a hold/charge. On cancellation within policy window, trigger refund. Use saga pattern for the book-then-pay flow to handle partial failures.

  7. How would you implement real-time availability updates for a web UI? Use WebSockets or Server-Sent Events (SSE). On every booking/cancellation, publish an event to a pub/sub channel per restaurant. Connected clients receive instant updates to refresh the availability grid.



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