โฑ๏ธ 24 min read
Designing a Delivery Slot Booking System
Difficulty: Intermediate Patterns: Template Method, Observer, Factory Asked at: Licious, BigBasket, Blinkit, Zepto
Functional Requirements
- Create time slots - define delivery windows with start time, end time, and max capacity
- Book slots with capacity - customers book a slot; booking succeeds only if capacity available
- Cancel bookings - customers can cancel; freed capacity becomes available again
- Check availability - query available slots for a given date/time range
- Conflict detection - prevent double-booking by same customer in overlapping slots
- Waitlist - when a slot is full, customers can join a waitlist and get auto-promoted on cancellation
Non-Functional Requirements
- Thread-safety - concurrent bookings must not exceed slot capacity
- Fairness - waitlist is FIFO; first to join gets promoted first
- Extensibility - new slot types (express, scheduled, same-day) without changing booking logic
Core Entities
| Entity | Description |
|---|---|
TimeSlot |
Delivery window: start, end, capacity, current bookings |
SlotType |
Enum: STANDARD, EXPRESS, SAME_DAY |
Booking |
Links a customer to a slot with status (CONFIRMED, WAITLISTED, CANCELLED) |
BookingStatus |
Enum: CONFIRMED, WAITLISTED, CANCELLED, PROMOTED |
Customer |
ID, name, address |
WaitlistEntry |
Customer + timestamp in waitlist queue |
SlotManager |
Creates and manages all time slots |
BookingService |
Orchestrates booking, cancellation, waitlist promotion |
BookingObserver |
Notified on booking events (confirmed, cancelled, promoted) |
SlotFactory |
Creates different types of slots with appropriate defaults |
Class Diagram
classDiagram
class SlotType {
<<enumeration>>
STANDARD
EXPRESS
SAME_DAY
}
class BookingStatus {
<<enumeration>>
CONFIRMED
WAITLISTED
CANCELLED
PROMOTED
}
class TimeSlot {
-String id
-LocalDateTime startTime
-LocalDateTime endTime
-int maxCapacity
-int currentBookings
-SlotType type
-Queue~WaitlistEntry~ waitlist
+isAvailable() boolean
+book() boolean
+release()
+addToWaitlist(Customer)
+promoteFromWaitlist() Customer
}
class Customer {
-String id
-String name
-String address
}
class Booking {
-String id
-Customer customer
-TimeSlot slot
-BookingStatus status
-LocalDateTime bookedAt
+confirm()
+cancel()
+waitlist()
}
class WaitlistEntry {
-Customer customer
-long timestamp
}
class SlotFactory {
+createSlot(SlotType, LocalDateTime start, LocalDateTime end) TimeSlot
}
class BookingObserver {
<<interface>>
+onBookingConfirmed(Booking)
+onBookingCancelled(Booking)
+onCustomerPromoted(Booking)
+onWaitlisted(Booking)
}
class BookingService {
-Map~String, TimeSlot~ slots
-Map~String, Booking~ bookings
-Map~String, List~Booking~~ customerBookings
-List~BookingObserver~ observers
+bookSlot(Customer, String slotId) Booking
+cancelBooking(String bookingId)
+getAvailableSlots(LocalDateTime from, LocalDateTime to) List
+getWaitlistPosition(String bookingId) int
}
BookingService --> TimeSlot
BookingService --> Booking
BookingService --> BookingObserver
Booking --> Customer
Booking --> TimeSlot
Booking --> BookingStatus
TimeSlot --> SlotType
TimeSlot --> WaitlistEntry
SlotFactory --> TimeSlot
Design Patterns
| Pattern | Where | Why |
|---|---|---|
| Template Method | Booking flow: validate โ reserve โ notify (subclass can override validation) | Common booking structure with customizable steps per slot type |
| Observer | BookingObserver notified on confirm/cancel/promote |
Decouple notifications (SMS, email, push) from booking logic |
| Factory | SlotFactory creates STANDARD/EXPRESS/SAME_DAY slots |
Encapsulate creation logic; each type has different capacity defaults |
Data Structures
| Component | Structure | Why |
|---|---|---|
| Slots | ConcurrentHashMap<String, TimeSlot> |
O(1) slot lookup by ID |
| Bookings | ConcurrentHashMap<String, Booking> |
O(1) booking lookup |
| Customer bookings | Map<String, List<Booking>> |
Track all bookings per customer for conflict detection |
| Waitlist per slot | LinkedList<WaitlistEntry> (FIFO queue) |
Fair ordering; O(1) add/remove from ends |
| Available slots | Filtered from all slots by time range | Linear scan acceptable for bounded slot count |
How It All Fits Together
Hereโs the complete flow for booking a delivery slot:
- Customer selects a time window โ calls
bookSlot(customer, slotId) - BookingService validates: does the slot exist? Is the customer already booked in an overlapping slot?
- If slot has capacity: decrement available count, create Booking with status CONFIRMED
- If slot is full: create Booking with status WAITLISTED, add to slotโs waitlist queue
- Observers notified (SMS/push sent to customer)
When a booking is cancelled:
- Customer calls
cancelBooking(bookingId) - Booking status โ CANCELLED, slot capacity released (+1)
- If waitlist is non-empty: promote first customer โ auto-confirm their booking
- Promoted customerโs booking status โ PROMOTED (then CONFIRMED)
- Observers notified for both cancellation and promotion
Complete Code
Models and Enums
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.time.*;
import java.time.format.*;
// โโโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
enum SlotType {
STANDARD, EXPRESS, SAME_DAY
}
enum BookingStatus {
CONFIRMED, WAITLISTED, CANCELLED, PROMOTED
}
// โโโ Customer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Customer {
private final String id;
private final String name;
private final String address;
public Customer(String id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public String getId() { return id; }
public String getName() { return name; }
public String getAddress() { return address; }
@Override
public String toString() { return "Customer[" + name + "]"; }
}
// โโโ Waitlist Entry โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class WaitlistEntry {
private final Customer customer;
private final long timestamp;
public WaitlistEntry(Customer customer) {
this.customer = customer;
this.timestamp = System.currentTimeMillis();
}
public Customer getCustomer() { return customer; }
public long getTimestamp() { return timestamp; }
}
// โโโ Time Slot โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TimeSlot {
private final String id;
private final LocalDateTime startTime;
private final LocalDateTime endTime;
private final int maxCapacity;
private int currentBookings;
private final SlotType type;
private final Queue<WaitlistEntry> waitlist;
public TimeSlot(String id, LocalDateTime start, LocalDateTime end,
int maxCapacity, SlotType type) {
this.id = id;
this.startTime = start;
this.endTime = end;
this.maxCapacity = maxCapacity;
this.currentBookings = 0;
this.type = type;
this.waitlist = new LinkedList<>();
}
public boolean isAvailable() {
return currentBookings < maxCapacity;
}
public boolean book() {
if (!isAvailable()) return false;
currentBookings++;
return true;
}
public void release() {
if (currentBookings > 0) currentBookings--;
}
public void addToWaitlist(Customer customer) {
waitlist.offer(new WaitlistEntry(customer));
}
public Customer promoteFromWaitlist() {
WaitlistEntry entry = waitlist.poll();
return entry != null ? entry.getCustomer() : null;
}
public int getWaitlistSize() { return waitlist.size(); }
public boolean overlaps(TimeSlot other) {
return this.startTime.isBefore(other.endTime) &&
other.startTime.isBefore(this.endTime);
}
public String getId() { return id; }
public LocalDateTime getStartTime() { return startTime; }
public LocalDateTime getEndTime() { return endTime; }
public int getMaxCapacity() { return maxCapacity; }
public int getCurrentBookings() { return currentBookings; }
public int getAvailableCapacity() { return maxCapacity - currentBookings; }
public SlotType getType() { return type; }
@Override
public String toString() {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("HH:mm");
return "Slot[" + id + " | " + type + " | " +
startTime.format(fmt) + "-" + endTime.format(fmt) +
" | " + getAvailableCapacity() + "/" + maxCapacity + " available" +
(waitlist.isEmpty() ? "" : " | waitlist=" + waitlist.size()) + "]";
}
}
// โโโ Booking โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Booking {
private final String id;
private final Customer customer;
private final TimeSlot slot;
private BookingStatus status;
private final LocalDateTime bookedAt;
public Booking(Customer customer, TimeSlot slot, BookingStatus status) {
this.id = "BKG-" + UUID.randomUUID().toString().substring(0, 6);
this.customer = customer;
this.slot = slot;
this.status = status;
this.bookedAt = LocalDateTime.now();
}
public void confirm() { this.status = BookingStatus.CONFIRMED; }
public void cancel() { this.status = BookingStatus.CANCELLED; }
public void waitlist() { this.status = BookingStatus.WAITLISTED; }
public void promote() { this.status = BookingStatus.PROMOTED; }
public String getId() { return id; }
public Customer getCustomer() { return customer; }
public TimeSlot getSlot() { return slot; }
public BookingStatus getStatus() { return status; }
@Override
public String toString() {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("HH:mm");
return "Booking[" + id + " | " + customer.getName() + " | " +
slot.getStartTime().format(fmt) + "-" + slot.getEndTime().format(fmt) +
" | " + status + "]";
}
}
// โโโ Slot Factory โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class SlotFactory {
private static int counter = 0;
public static TimeSlot createSlot(SlotType type, LocalDateTime start, LocalDateTime end) {
int capacity;
switch (type) {
case EXPRESS: capacity = 5; break;
case SAME_DAY: capacity = 10; break;
default: capacity = 20; break;
}
return new TimeSlot("SLOT-" + (++counter), start, end, capacity, type);
}
public static TimeSlot createSlot(SlotType type, LocalDateTime start,
LocalDateTime end, int capacity) {
return new TimeSlot("SLOT-" + (++counter), start, end, capacity, type);
}
}
// โโโ Observer Interface โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
interface BookingObserver {
void onBookingConfirmed(Booking booking);
void onBookingCancelled(Booking booking);
void onCustomerPromoted(Booking booking);
void onWaitlisted(Booking booking);
}
// โโโ Notification Observer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class NotificationObserver implements BookingObserver {
@Override
public void onBookingConfirmed(Booking b) {
System.out.println(" [NOTIFY] Booking confirmed for " + b.getCustomer().getName() +
" - " + b.getSlot().getType() + " slot");
}
@Override
public void onBookingCancelled(Booking b) {
System.out.println(" [NOTIFY] Booking cancelled for " + b.getCustomer().getName());
}
@Override
public void onCustomerPromoted(Booking b) {
System.out.println(" [NOTIFY] " + b.getCustomer().getName() +
" promoted from waitlist! Booking confirmed.");
}
@Override
public void onWaitlisted(Booking b) {
System.out.println(" [NOTIFY] " + b.getCustomer().getName() +
" added to waitlist (position: " +
b.getSlot().getWaitlistSize() + ")");
}
}
// โโโ Booking Service โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class BookingService {
private final ConcurrentHashMap<String, TimeSlot> slots;
private final ConcurrentHashMap<String, Booking> bookings;
private final ConcurrentHashMap<String, List<Booking>> customerBookings;
private final List<BookingObserver> observers;
private final ReentrantLock lock;
public BookingService() {
this.slots = new ConcurrentHashMap<>();
this.bookings = new ConcurrentHashMap<>();
this.customerBookings = new ConcurrentHashMap<>();
this.observers = new CopyOnWriteArrayList<>();
this.lock = new ReentrantLock();
}
public void addObserver(BookingObserver observer) {
observers.add(observer);
}
public void addSlot(TimeSlot slot) {
slots.put(slot.getId(), slot);
}
public Booking bookSlot(Customer customer, String slotId) {
lock.lock();
try {
TimeSlot slot = slots.get(slotId);
if (slot == null) {
throw new RuntimeException("Slot not found: " + slotId);
}
// Conflict detection: check overlapping bookings for this customer
if (hasConflict(customer, slot)) {
throw new RuntimeException("Customer " + customer.getName() +
" already has a booking in an overlapping time slot");
}
Booking booking;
if (slot.isAvailable()) {
slot.book();
booking = new Booking(customer, slot, BookingStatus.CONFIRMED);
observers.forEach(o -> o.onBookingConfirmed(booking));
} else {
// Add to waitlist
slot.addToWaitlist(customer);
booking = new Booking(customer, slot, BookingStatus.WAITLISTED);
observers.forEach(o -> o.onWaitlisted(booking));
}
bookings.put(booking.getId(), booking);
customerBookings.computeIfAbsent(customer.getId(), k -> new ArrayList<>())
.add(booking);
return booking;
} finally {
lock.unlock();
}
}
public void cancelBooking(String bookingId) {
lock.lock();
try {
Booking booking = bookings.get(bookingId);
if (booking == null) {
throw new RuntimeException("Booking not found: " + bookingId);
}
if (booking.getStatus() == BookingStatus.CANCELLED) {
throw new RuntimeException("Booking already cancelled");
}
BookingStatus previousStatus = booking.getStatus();
booking.cancel();
observers.forEach(o -> o.onBookingCancelled(booking));
if (previousStatus == BookingStatus.CONFIRMED) {
TimeSlot slot = booking.getSlot();
slot.release();
// Promote from waitlist
Customer promoted = slot.promoteFromWaitlist();
if (promoted != null) {
slot.book();
// Find the waitlisted booking for this customer
Booking promotedBooking = findWaitlistedBooking(promoted, slot);
if (promotedBooking != null) {
promotedBooking.confirm();
observers.forEach(o -> o.onCustomerPromoted(promotedBooking));
}
}
}
} finally {
lock.unlock();
}
}
private Booking findWaitlistedBooking(Customer customer, TimeSlot slot) {
List<Booking> custBookings = customerBookings.get(customer.getId());
if (custBookings == null) return null;
return custBookings.stream()
.filter(b -> b.getSlot().getId().equals(slot.getId()))
.filter(b -> b.getStatus() == BookingStatus.WAITLISTED)
.findFirst()
.orElse(null);
}
private boolean hasConflict(Customer customer, TimeSlot targetSlot) {
List<Booking> existing = customerBookings.get(customer.getId());
if (existing == null) return false;
return existing.stream()
.filter(b -> b.getStatus() == BookingStatus.CONFIRMED)
.anyMatch(b -> b.getSlot().overlaps(targetSlot));
}
public List<TimeSlot> getAvailableSlots(LocalDateTime from, LocalDateTime to) {
List<TimeSlot> available = new ArrayList<>();
for (TimeSlot slot : slots.values()) {
if (!slot.getStartTime().isBefore(from) &&
!slot.getEndTime().isAfter(to) &&
slot.isAvailable()) {
available.add(slot);
}
}
available.sort(Comparator.comparing(TimeSlot::getStartTime));
return available;
}
public List<TimeSlot> getAllSlots() {
List<TimeSlot> all = new ArrayList<>(slots.values());
all.sort(Comparator.comparing(TimeSlot::getStartTime));
return all;
}
public int getWaitlistPosition(String bookingId) {
Booking booking = bookings.get(bookingId);
if (booking == null || booking.getStatus() != BookingStatus.WAITLISTED) return -1;
// Position is approximate since we can't peek into LinkedList efficiently
return booking.getSlot().getWaitlistSize();
}
public void displaySlots() {
System.out.println("\n--- All Delivery Slots ---");
for (TimeSlot slot : getAllSlots()) {
System.out.println(" " + slot);
}
}
}
// โโโ Main Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
public class DeliverySlotBookingDemo {
public static void main(String[] args) {
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
System.out.println(" DELIVERY SLOT BOOKING - LLD DEMO ");
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
BookingService service = new BookingService();
service.addObserver(new NotificationObserver());
// โโโ Create Slots โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("--- Creating Delivery Slots ---");
LocalDate today = LocalDate.now();
TimeSlot morning = SlotFactory.createSlot(SlotType.STANDARD,
today.atTime(9, 0), today.atTime(11, 0), 3);
TimeSlot midday = SlotFactory.createSlot(SlotType.EXPRESS,
today.atTime(11, 0), today.atTime(13, 0), 2);
TimeSlot afternoon = SlotFactory.createSlot(SlotType.STANDARD,
today.atTime(14, 0), today.atTime(16, 0), 3);
TimeSlot evening = SlotFactory.createSlot(SlotType.SAME_DAY,
today.atTime(18, 0), today.atTime(21, 0), 2);
service.addSlot(morning);
service.addSlot(midday);
service.addSlot(afternoon);
service.addSlot(evening);
service.displaySlots();
// โโโ Create Customers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Customer alice = new Customer("C1", "Alice", "123 Main St");
Customer bob = new Customer("C2", "Bob", "456 Oak Ave");
Customer charlie = new Customer("C3", "Charlie", "789 Pine Rd");
Customer diana = new Customer("C4", "Diana", "321 Elm St");
Customer eve = new Customer("C5", "Eve", "654 Maple Dr");
// โโโ Book Slots โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Booking Slots ---");
Booking b1 = service.bookSlot(alice, morning.getId());
System.out.println(" " + b1);
Booking b2 = service.bookSlot(bob, morning.getId());
System.out.println(" " + b2);
Booking b3 = service.bookSlot(charlie, morning.getId());
System.out.println(" " + b3);
// Morning is now full (capacity=3) - next booking goes to waitlist
System.out.println("\n--- Morning Slot Full - Waitlist ---");
Booking b4 = service.bookSlot(diana, morning.getId());
System.out.println(" " + b4);
Booking b5 = service.bookSlot(eve, morning.getId());
System.out.println(" " + b5);
service.displaySlots();
// โโโ Cancel and Promote โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Cancel Alice's Booking (auto-promote Diana) ---");
service.cancelBooking(b1.getId());
System.out.println(" Alice's booking: " + b1.getStatus());
System.out.println(" Diana's booking: " + b4.getStatus());
service.displaySlots();
// โโโ Conflict Detection โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Conflict Detection ---");
// Bob already has morning slot - try overlapping
try {
// Create an overlapping slot for testing
TimeSlot overlap = SlotFactory.createSlot(SlotType.EXPRESS,
today.atTime(10, 0), today.atTime(12, 0), 5);
service.addSlot(overlap);
service.bookSlot(bob, overlap.getId());
} catch (RuntimeException e) {
System.out.println(" Expected: " + e.getMessage());
}
// โโโ Check Availability โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Available Slots (14:00 - 21:00) ---");
List<TimeSlot> available = service.getAvailableSlots(
today.atTime(14, 0), today.atTime(21, 0));
for (TimeSlot slot : available) {
System.out.println(" " + slot);
}
// โโโ Express Slot Booking โโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Express Slot Booking ---");
Booking b6 = service.bookSlot(alice, midday.getId());
System.out.println(" " + b6);
Booking b7 = service.bookSlot(charlie, midday.getId());
System.out.println(" " + b7);
// Express now full
Booking b8 = service.bookSlot(eve, midday.getId());
System.out.println(" " + b8); // waitlisted
// โโโ Cancel from Express Slot โโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Cancel Charlie from Express (promote Eve) ---");
service.cancelBooking(b7.getId());
service.displaySlots();
System.out.println("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
System.out.println(" DEMO COMPLETE ");
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
}
}
import threading
import uuid
from datetime import datetime, timedelta
from collections import deque
from enum import Enum
from abc import ABC, abstractmethod
from typing import Optional
# โโโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class SlotType(Enum):
STANDARD = "STANDARD"
EXPRESS = "EXPRESS"
SAME_DAY = "SAME_DAY"
class BookingStatus(Enum):
CONFIRMED = "CONFIRMED"
WAITLISTED = "WAITLISTED"
CANCELLED = "CANCELLED"
PROMOTED = "PROMOTED"
# โโโ Customer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Customer:
def __init__(self, customer_id: str, name: str, address: str):
self.id = customer_id
self.name = name
self.address = address
def __str__(self) -> str:
return f"Customer[{self.name}]"
# โโโ Waitlist Entry โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class WaitlistEntry:
def __init__(self, customer: Customer):
self.customer = customer
self.timestamp = datetime.now()
# โโโ Time Slot โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TimeSlot:
_counter = 0
def __init__(self, slot_id: str, start_time: datetime, end_time: datetime,
max_capacity: int, slot_type: SlotType):
self.id = slot_id
self.start_time = start_time
self.end_time = end_time
self.max_capacity = max_capacity
self.current_bookings = 0
self.type = slot_type
self.waitlist: deque[WaitlistEntry] = deque()
@property
def is_available(self) -> bool:
return self.current_bookings < self.max_capacity
@property
def available_capacity(self) -> int:
return self.max_capacity - self.current_bookings
def book(self) -> bool:
if not self.is_available:
return False
self.current_bookings += 1
return True
def release(self):
if self.current_bookings > 0:
self.current_bookings -= 1
def add_to_waitlist(self, customer: Customer):
self.waitlist.append(WaitlistEntry(customer))
def promote_from_waitlist(self) -> Optional[Customer]:
if self.waitlist:
entry = self.waitlist.popleft()
return entry.customer
return None
@property
def waitlist_size(self) -> int:
return len(self.waitlist)
def overlaps(self, other: "TimeSlot") -> bool:
return self.start_time < other.end_time and other.start_time < self.end_time
def __str__(self) -> str:
fmt = "%H:%M"
wl = f" | waitlist={self.waitlist_size}" if self.waitlist else ""
return (f"Slot[{self.id} | {self.type.value} | "
f"{self.start_time.strftime(fmt)}-{self.end_time.strftime(fmt)} | "
f"{self.available_capacity}/{self.max_capacity} available{wl}]")
# โโโ Booking โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Booking:
def __init__(self, customer: Customer, slot: TimeSlot, status: BookingStatus):
self.id = f"BKG-{uuid.uuid4().hex[:6]}"
self.customer = customer
self.slot = slot
self.status = status
self.booked_at = datetime.now()
def confirm(self): self.status = BookingStatus.CONFIRMED
def cancel(self): self.status = BookingStatus.CANCELLED
def waitlisted(self): self.status = BookingStatus.WAITLISTED
def promote(self): self.status = BookingStatus.PROMOTED
def __str__(self) -> str:
fmt = "%H:%M"
return (f"Booking[{self.id} | {self.customer.name} | "
f"{self.slot.start_time.strftime(fmt)}-{self.slot.end_time.strftime(fmt)} | "
f"{self.status.value}]")
# โโโ Slot Factory โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class SlotFactory:
_counter = 0
@classmethod
def create_slot(cls, slot_type: SlotType, start: datetime, end: datetime,
capacity: Optional[int] = None) -> TimeSlot:
cls._counter += 1
if capacity is None:
capacity = {SlotType.STANDARD: 20, SlotType.EXPRESS: 5, SlotType.SAME_DAY: 10}[slot_type]
return TimeSlot(f"SLOT-{cls._counter}", start, end, capacity, slot_type)
# โโโ Observer Interface โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class BookingObserver(ABC):
@abstractmethod
def on_booking_confirmed(self, booking: Booking): pass
@abstractmethod
def on_booking_cancelled(self, booking: Booking): pass
@abstractmethod
def on_customer_promoted(self, booking: Booking): pass
@abstractmethod
def on_waitlisted(self, booking: Booking): pass
class NotificationObserver(BookingObserver):
def on_booking_confirmed(self, b):
print(f" [NOTIFY] Booking confirmed for {b.customer.name} - {b.slot.type.value} slot")
def on_booking_cancelled(self, b):
print(f" [NOTIFY] Booking cancelled for {b.customer.name}")
def on_customer_promoted(self, b):
print(f" [NOTIFY] {b.customer.name} promoted from waitlist! Booking confirmed.")
def on_waitlisted(self, b):
print(f" [NOTIFY] {b.customer.name} added to waitlist (position: {b.slot.waitlist_size})")
# โโโ Booking Service โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class BookingService:
def __init__(self):
self._slots: dict[str, TimeSlot] = {}
self._bookings: dict[str, Booking] = {}
self._customer_bookings: dict[str, list[Booking]] = {}
self._observers: list[BookingObserver] = []
self._lock = threading.Lock()
def add_observer(self, observer: BookingObserver):
self._observers.append(observer)
def add_slot(self, slot: TimeSlot):
self._slots[slot.id] = slot
def book_slot(self, customer: Customer, slot_id: str) -> Booking:
with self._lock:
slot = self._slots.get(slot_id)
if not slot:
raise RuntimeError(f"Slot not found: {slot_id}")
if self._has_conflict(customer, slot):
raise RuntimeError(
f"Customer {customer.name} already has a booking in an overlapping time slot")
if slot.is_available:
slot.book()
booking = Booking(customer, slot, BookingStatus.CONFIRMED)
for obs in self._observers:
obs.on_booking_confirmed(booking)
else:
slot.add_to_waitlist(customer)
booking = Booking(customer, slot, BookingStatus.WAITLISTED)
for obs in self._observers:
obs.on_waitlisted(booking)
self._bookings[booking.id] = booking
self._customer_bookings.setdefault(customer.id, []).append(booking)
return booking
def cancel_booking(self, booking_id: str):
with self._lock:
booking = self._bookings.get(booking_id)
if not booking:
raise RuntimeError(f"Booking not found: {booking_id}")
if booking.status == BookingStatus.CANCELLED:
raise RuntimeError("Booking already cancelled")
prev_status = booking.status
booking.cancel()
for obs in self._observers:
obs.on_booking_cancelled(booking)
if prev_status == BookingStatus.CONFIRMED:
slot = booking.slot
slot.release()
promoted_customer = slot.promote_from_waitlist()
if promoted_customer:
slot.book()
promoted_booking = self._find_waitlisted_booking(promoted_customer, slot)
if promoted_booking:
promoted_booking.confirm()
for obs in self._observers:
obs.on_customer_promoted(promoted_booking)
def _find_waitlisted_booking(self, customer: Customer, slot: TimeSlot) -> Optional[Booking]:
cust_bookings = self._customer_bookings.get(customer.id, [])
for b in cust_bookings:
if b.slot.id == slot.id and b.status == BookingStatus.WAITLISTED:
return b
return None
def _has_conflict(self, customer: Customer, target_slot: TimeSlot) -> bool:
existing = self._customer_bookings.get(customer.id, [])
return any(
b.status == BookingStatus.CONFIRMED and b.slot.overlaps(target_slot)
for b in existing
)
def get_available_slots(self, from_time: datetime, to_time: datetime) -> list[TimeSlot]:
available = [
s for s in self._slots.values()
if s.start_time >= from_time and s.end_time <= to_time and s.is_available
]
return sorted(available, key=lambda s: s.start_time)
def display_slots(self):
print("\n--- All Delivery Slots ---")
for slot in sorted(self._slots.values(), key=lambda s: s.start_time):
print(f" {slot}")
# โโโ Main Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
def main():
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
print(" DELIVERY SLOT BOOKING - LLD DEMO ")
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n")
service = BookingService()
service.add_observer(NotificationObserver())
print("--- Creating Delivery Slots ---")
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
morning = SlotFactory.create_slot(SlotType.STANDARD,
today.replace(hour=9), today.replace(hour=11), 3)
midday = SlotFactory.create_slot(SlotType.EXPRESS,
today.replace(hour=11), today.replace(hour=13), 2)
afternoon = SlotFactory.create_slot(SlotType.STANDARD,
today.replace(hour=14), today.replace(hour=16), 3)
evening = SlotFactory.create_slot(SlotType.SAME_DAY,
today.replace(hour=18), today.replace(hour=21), 2)
for slot in [morning, midday, afternoon, evening]:
service.add_slot(slot)
service.display_slots()
alice = Customer("C1", "Alice", "123 Main St")
bob = Customer("C2", "Bob", "456 Oak Ave")
charlie = Customer("C3", "Charlie", "789 Pine Rd")
diana = Customer("C4", "Diana", "321 Elm St")
eve = Customer("C5", "Eve", "654 Maple Dr")
print("\n--- Booking Slots ---")
b1 = service.book_slot(alice, morning.id)
print(f" {b1}")
b2 = service.book_slot(bob, morning.id)
print(f" {b2}")
b3 = service.book_slot(charlie, morning.id)
print(f" {b3}")
print("\n--- Morning Slot Full - Waitlist ---")
b4 = service.book_slot(diana, morning.id)
print(f" {b4}")
b5 = service.book_slot(eve, morning.id)
print(f" {b5}")
service.display_slots()
print("\n--- Cancel Alice's Booking (auto-promote Diana) ---")
service.cancel_booking(b1.id)
print(f" Alice's booking: {b1.status.value}")
print(f" Diana's booking: {b4.status.value}")
service.display_slots()
print("\n--- Conflict Detection ---")
try:
overlap = SlotFactory.create_slot(SlotType.EXPRESS,
today.replace(hour=10), today.replace(hour=12), 5)
service.add_slot(overlap)
service.book_slot(bob, overlap.id)
except RuntimeError as e:
print(f" Expected: {e}")
print("\n--- Available Slots (14:00 - 21:00) ---")
available = service.get_available_slots(today.replace(hour=14), today.replace(hour=21))
for slot in available:
print(f" {slot}")
print("\n--- Express Slot Booking ---")
b6 = service.book_slot(alice, midday.id)
print(f" {b6}")
b7 = service.book_slot(charlie, midday.id)
print(f" {b7}")
b8 = service.book_slot(eve, midday.id)
print(f" {b8}")
print("\n--- Cancel Charlie from Express (promote Eve) ---")
service.cancel_booking(b7.id)
service.display_slots()
print("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
print(" DEMO COMPLETE ")
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
if __name__ == "__main__":
main()
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <queue>
#include <mutex>
#include <memory>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <random>
// โโโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
enum class SlotType { STANDARD, EXPRESS, SAME_DAY };
enum class BookingStatus { CONFIRMED, WAITLISTED, CANCELLED, PROMOTED };
std::string slotTypeStr(SlotType t) {
switch (t) {
case SlotType::STANDARD: return "STANDARD";
case SlotType::EXPRESS: return "EXPRESS";
case SlotType::SAME_DAY: return "SAME_DAY";
}
return "";
}
std::string bookingStatusStr(BookingStatus s) {
switch (s) {
case BookingStatus::CONFIRMED: return "CONFIRMED";
case BookingStatus::WAITLISTED: return "WAITLISTED";
case BookingStatus::CANCELLED: return "CANCELLED";
case BookingStatus::PROMOTED: return "PROMOTED";
}
return "";
}
std::string genBookingId() {
static std::mt19937 gen(std::random_device{}());
static std::uniform_int_distribution<int> dist(0, 15);
static const char* hex = "0123456789abcdef";
std::string id = "BKG-";
for (int i = 0; i < 6; ++i) id += hex[dist(gen)];
return id;
}
// โโโ Simple Time Representation โโโโโโโโโโโโโโโโโโโโโโโโโ
struct SlotTime {
int hour, minute;
bool operator<(const SlotTime& o) const {
return hour * 60 + minute < o.hour * 60 + o.minute;
}
bool operator>=(const SlotTime& o) const { return !(*this < o); }
bool operator<=(const SlotTime& o) const { return !(o < *this); }
std::string str() const {
std::ostringstream ss;
ss << std::setfill('0') << std::setw(2) << hour << ":"
<< std::setfill('0') << std::setw(2) << minute;
return ss.str();
}
};
// โโโ Customer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
struct Customer {
std::string id, name, address;
};
// โโโ Time Slot โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TimeSlot {
public:
std::string id;
SlotTime startTime, endTime;
int maxCapacity, currentBookings = 0;
SlotType type;
std::queue<Customer*> waitlist;
TimeSlot(std::string id, SlotTime start, SlotTime end, int cap, SlotType type)
: id(std::move(id)), startTime(start), endTime(end), maxCapacity(cap), type(type) {}
bool isAvailable() const { return currentBookings < maxCapacity; }
int availableCapacity() const { return maxCapacity - currentBookings; }
bool book() {
if (!isAvailable()) return false;
currentBookings++;
return true;
}
void release() { if (currentBookings > 0) currentBookings--; }
void addToWaitlist(Customer* c) { waitlist.push(c); }
Customer* promoteFromWaitlist() {
if (waitlist.empty()) return nullptr;
Customer* c = waitlist.front();
waitlist.pop();
return c;
}
bool overlaps(const TimeSlot& other) const {
return startTime < other.endTime && other.startTime < endTime;
}
std::string toString() const {
std::ostringstream ss;
ss << "Slot[" << id << " | " << slotTypeStr(type) << " | "
<< startTime.str() << "-" << endTime.str() << " | "
<< availableCapacity() << "/" << maxCapacity << " available";
if (!waitlist.empty()) ss << " | waitlist=" << waitlist.size();
ss << "]";
return ss.str();
}
};
// โโโ Booking โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
struct Booking {
std::string id;
Customer* customer;
TimeSlot* slot;
BookingStatus status;
Booking(Customer* c, TimeSlot* s, BookingStatus st)
: id(genBookingId()), customer(c), slot(s), status(st) {}
std::string toString() const {
return "Booking[" + id + " | " + customer->name + " | " +
slot->startTime.str() + "-" + slot->endTime.str() + " | " +
bookingStatusStr(status) + "]";
}
};
// โโโ Slot Factory โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class SlotFactory {
static int counter;
public:
static std::unique_ptr<TimeSlot> create(SlotType type, SlotTime start, SlotTime end, int cap) {
return std::make_unique<TimeSlot>("SLOT-" + std::to_string(++counter), start, end, cap, type);
}
};
int SlotFactory::counter = 0;
// โโโ Booking Service โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class BookingService {
std::unordered_map<std::string, TimeSlot*> slots;
std::vector<std::unique_ptr<TimeSlot>> slotStorage;
std::unordered_map<std::string, std::unique_ptr<Booking>> bookings;
std::unordered_map<std::string, std::vector<Booking*>> customerBookings;
std::mutex mtx;
public:
void addSlot(std::unique_ptr<TimeSlot> slot) {
TimeSlot* ptr = slot.get();
slots[ptr->id] = ptr;
slotStorage.push_back(std::move(slot));
}
Booking* bookSlot(Customer* customer, const std::string& slotId) {
std::lock_guard<std::mutex> lock(mtx);
auto it = slots.find(slotId);
if (it == slots.end()) throw std::runtime_error("Slot not found: " + slotId);
TimeSlot* slot = it->second;
if (hasConflict(customer, slot))
throw std::runtime_error(customer->name + " already has overlapping booking");
std::unique_ptr<Booking> booking;
if (slot->isAvailable()) {
slot->book();
booking = std::make_unique<Booking>(customer, slot, BookingStatus::CONFIRMED);
std::cout << " [NOTIFY] Confirmed for " << customer->name << "\n";
} else {
slot->addToWaitlist(customer);
booking = std::make_unique<Booking>(customer, slot, BookingStatus::WAITLISTED);
std::cout << " [NOTIFY] " << customer->name << " waitlisted (pos: "
<< slot->waitlist.size() << ")\n";
}
Booking* ptr = booking.get();
customerBookings[customer->id].push_back(ptr);
bookings[ptr->id] = std::move(booking);
return ptr;
}
void cancelBooking(const std::string& bookingId) {
std::lock_guard<std::mutex> lock(mtx);
auto it = bookings.find(bookingId);
if (it == bookings.end()) throw std::runtime_error("Booking not found");
Booking* booking = it->second.get();
BookingStatus prev = booking->status;
booking->status = BookingStatus::CANCELLED;
std::cout << " [NOTIFY] Cancelled for " << booking->customer->name << "\n";
if (prev == BookingStatus::CONFIRMED) {
TimeSlot* slot = booking->slot;
slot->release();
Customer* promoted = slot->promoteFromWaitlist();
if (promoted) {
slot->book();
Booking* pb = findWaitlisted(promoted, slot);
if (pb) {
pb->status = BookingStatus::CONFIRMED;
std::cout << " [NOTIFY] " << promoted->name << " promoted from waitlist!\n";
}
}
}
}
void displaySlots() {
std::cout << "\n--- All Delivery Slots ---\n";
for (auto& s : slotStorage) std::cout << " " << s->toString() << "\n";
}
private:
bool hasConflict(Customer* customer, TimeSlot* target) {
auto it = customerBookings.find(customer->id);
if (it == customerBookings.end()) return false;
for (auto* b : it->second) {
if (b->status == BookingStatus::CONFIRMED && b->slot->overlaps(*target))
return true;
}
return false;
}
Booking* findWaitlisted(Customer* customer, TimeSlot* slot) {
auto it = customerBookings.find(customer->id);
if (it == customerBookings.end()) return nullptr;
for (auto* b : it->second) {
if (b->slot == slot && b->status == BookingStatus::WAITLISTED)
return b;
}
return nullptr;
}
};
// โโโ Main Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
int main() {
std::cout << "=======================================\n";
std::cout << " DELIVERY SLOT BOOKING - LLD DEMO \n";
std::cout << "=======================================\n\n";
BookingService service;
std::cout << "--- Creating Delivery Slots ---\n";
service.addSlot(SlotFactory::create(SlotType::STANDARD, {9,0}, {11,0}, 3));
service.addSlot(SlotFactory::create(SlotType::EXPRESS, {11,0}, {13,0}, 2));
service.addSlot(SlotFactory::create(SlotType::STANDARD, {14,0}, {16,0}, 3));
service.addSlot(SlotFactory::create(SlotType::SAME_DAY, {18,0}, {21,0}, 2));
service.displaySlots();
Customer alice{"C1", "Alice", "123 Main St"};
Customer bob{"C2", "Bob", "456 Oak Ave"};
Customer charlie{"C3", "Charlie", "789 Pine Rd"};
Customer diana{"C4", "Diana", "321 Elm St"};
Customer eve{"C5", "Eve", "654 Maple Dr"};
std::cout << "\n--- Booking Slots ---\n";
auto* b1 = service.bookSlot(&alice, "SLOT-1");
std::cout << " " << b1->toString() << "\n";
auto* b2 = service.bookSlot(&bob, "SLOT-1");
std::cout << " " << b2->toString() << "\n";
auto* b3 = service.bookSlot(&charlie, "SLOT-1");
std::cout << " " << b3->toString() << "\n";
std::cout << "\n--- Morning Full - Waitlist ---\n";
auto* b4 = service.bookSlot(&diana, "SLOT-1");
std::cout << " " << b4->toString() << "\n";
auto* b5 = service.bookSlot(&eve, "SLOT-1");
std::cout << " " << b5->toString() << "\n";
service.displaySlots();
std::cout << "\n--- Cancel Alice (promote Diana) ---\n";
service.cancelBooking(b1->id);
std::cout << " Alice: " << bookingStatusStr(b1->status) << "\n";
std::cout << " Diana: " << bookingStatusStr(b4->status) << "\n";
service.displaySlots();
std::cout << "\n--- Conflict Detection ---\n";
try {
service.addSlot(SlotFactory::create(SlotType::EXPRESS, {10,0}, {12,0}, 5));
service.bookSlot(&bob, "SLOT-5");
} catch (const std::runtime_error& e) {
std::cout << " Expected: " << e.what() << "\n";
}
std::cout << "\n--- Express Slot Booking ---\n";
auto* b6 = service.bookSlot(&alice, "SLOT-2");
std::cout << " " << b6->toString() << "\n";
auto* b7 = service.bookSlot(&charlie, "SLOT-2");
std::cout << " " << b7->toString() << "\n";
auto* b8 = service.bookSlot(&eve, "SLOT-2");
std::cout << " " << b8->toString() << "\n";
std::cout << "\n--- Cancel Charlie from Express (promote Eve) ---\n";
service.cancelBooking(b7->id);
service.displaySlots();
std::cout << "\n=======================================\n";
std::cout << " DEMO COMPLETE \n";
std::cout << "=======================================\n";
return 0;
}
State Transitions
stateDiagram-v2
[*] --> CONFIRMED : capacity available
[*] --> WAITLISTED : slot full
CONFIRMED --> CANCELLED : customer cancels
WAITLISTED --> CONFIRMED : promoted from waitlist
WAITLISTED --> CANCELLED : customer cancels
CANCELLED --> [*]
CONFIRMED --> [*]
Sequence Diagram - Book and Promote Flow
sequenceDiagram
participant C1 as Customer1
participant C2 as Customer2
participant BS as BookingService
participant Slot as TimeSlot
participant Obs as Observer
C1->>BS: bookSlot(slotId)
BS->>Slot: isAvailable()
Slot-->>BS: false (full)
BS->>Slot: addToWaitlist(C1)
BS->>Obs: onWaitlisted(booking)
BS-->>C1: Booking (WAITLISTED)
Note over BS: Another customer cancels
C2->>BS: cancelBooking(bookingId)
BS->>Slot: release()
BS->>Slot: promoteFromWaitlist()
Slot-->>BS: Customer1
BS->>Slot: book()
BS->>Obs: onCustomerPromoted(booking)
Obs-->>C1: notification sent
How to Extend
| Extension | Implementation |
|---|---|
| Recurring slots | Template for daily/weekly slot generation |
| Priority waitlist | Replace FIFO queue with priority queue (premium customers first) |
| Delivery zones | Add zone to TimeSlot; filter by customerโs zone |
| Dynamic capacity | Adjust capacity based on driver availability / weather |
| Slot pricing | Express costs more; surge pricing during high demand |
| Notification channels | Multiple observers: SMS, email, push notification |
| Slot locking | Temporary hold (5 min) during checkout to prevent race |
What Interviewers Look For
- โ Capacity management - atomic book/release with thread-safety
- โ Waitlist FIFO - fair auto-promotion on cancellation
- โ Conflict detection - overlapping slot check per customer
- โ Factory pattern - slot creation with type-specific defaults
- โ Observer pattern - decoupled notifications
- โ Template method - standardized booking flow
- โ Clean state management - booking status transitions
- โ Concurrency - locks prevent overbooking
Related Concepts
Scale this design past a single process and these are the concepts it runs into:
- Distributed Locking โ โ the in-process lock that stops overbooking a slot becomes a Redis lock once booking runs on more than one node
- Transactions & Isolation Levels โ โ reserving capacity is a read-modify-write; without isolation two bookings both read the same free count
- Idempotency โ โ a retried booking request must consume slot capacity exactly once
- Message Queues โ โ waitlist promotion and confirmation notices belong on a queue, not inline in the booking call
Discussion
Newest first