Meeting Scheduler β 75-Minute Interview Guide
Quick Start
What is it? Calendar and meeting scheduling system with room booking, conflict detection, invitations, recurring meetings, and reminders using singleton coordinator, observer patterns for notifications, and strategy patterns for conflict resolution.
Key Classes:
- MeetingScheduler (Singleton): Centralized coordinator for all meetings, users, and rooms
- Meeting: Calendar event with attendees, time, and state machine lifecycle
- User: Professional user with personal calendar and availability
- Room: Physical room with capacity and booking constraints
- Invitation: Invitation with state machine (PendingβAccepted/Declined/Tentative)
- ConflictResolutionStrategy (ABC): Pluggable conflict handling (AutoReject, ProposeAlternatives, ForceBook)
- MeetingObserver (ABC): Event notification subscribers
Core Flows: 1. Meeting Creation: User creates meeting β Choose attendees & room β Check conflicts β Send invitations 2. Conflict Detection: Check room availability, attendee calendars, time overlaps β Apply resolution strategy 3. Invitation Flow: Send invitation β State Pending β Accept/Decline/Tentative β Update calendar 4. Recurring Meetings: Define recurrence rule β Generate instances β Check conflicts for each β Expansion strategy 5. Room Booking: Request room β Check capacity & availability β Book β Add to calendar
5 Design Patterns: - Singleton: One MeetingScheduler instance (centralized calendar system) - Observer: Notification system (attendees, room managers, notification channels) - Strategy: Conflict resolution (AutoReject, ProposeAlternatives, ForceBook) - State Machine: Meeting lifecycle (ScheduledβOngoingβCompleted/Cancelled) - Factory: Create meeting types (OneOffMeeting, RecurringMeeting)
System Overview
Professional meeting scheduling system enabling users to create meetings, book conference rooms, detect scheduling conflicts, manage invitations, and receive notifications.
Requirements
Functional: - Create one-time and recurring meetings with title, time, duration, location - Add/remove attendees and send invitations - Accept/decline/tentative invitations (state machine) - Book conference rooms with capacity constraints - Detect conflicts (time overlap, room double-booking, attendee unavailability) - Apply conflict resolution strategies (auto-reject, propose alternatives, force-book) - View personal calendar with meetings and availability - Find available time slots for group meetings - Cancel/reschedule meetings with notification - Set reminders for upcoming meetings - Support recurring meetings (daily, weekly, monthly) with exception handling
Non-Functional: - Conflict detection < 100ms (interval trees for O(log n) queries) - Calendar view < 500ms (pagination for large calendars) - Support 1M+ users with 10M+ meetings - 99.9% availability - Real-time notifications - Eventual consistency acceptable for sync
Constraints: - Single-process demo (production: microservices) - In-memory storage (production: PostgreSQL + Redis) - Optional scaling narratives
Architecture Diagram (ASCII UML)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MeetingScheduler (Singleton) β
β - users: {user_id β User} β
β - meetings: {meeting_id β Meeting} β
β - rooms: {room_id β Room} β
β - invitations: {inv_id β Invitation} β
β - conflict_strategy: ConflictResolutionStrategy β
β - observers: [MeetingObserver] β
β + create_meeting(), book_room(), find_free_slots() β
β + check_conflicts(), resolve_conflicts() β
β + set_conflict_resolution(strategy) β
ββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββΌβββββββββββββ¬βββββββββββββββ¬βββββββββββββββ
βΌ βΌ βΌ βΌ βΌ
ββββββββββ ββββββββββββ ββββββββββ ββββββββββ ββββββββββββ
β User β β Meeting β β Room β βInvita- β βRecurring β
ββββββββββ€ ββββββββββββ€ ββββββββββ€ βtion β βMeeting β
β-calendarβ β-title β β-name β ββββββββββ€ ββββββββββββ€
β-avail. β β-time β β-capacityβ β-status:β β-rule β
β-pref. β β-attendeesβ β-bookingsβ βPending β β-count β
ββββββββββ β-room β ββββββββββ β-Accept β β-except. β
β-status β β-Declineβ ββββββββββββ
β-recur β β-Tenta. β
ββββββββββββ ββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββ ββββββββββββββββββββββββ
β MeetingState β β Invitation State β
β (Abstract) β β (State Pattern) β
ββββββββββββββββββββ€ ββββββββββββββββββββββββ€
β SCHEDULED β β PENDING β
β β ONGOING β β β Accept/Decline β
β β COMPLETED β β β ACCEPTED/DECLINED β
β β CANCELLED β β β Tentative β
β β CANCELLED β β β TENTATIVE β
ββββββββββββββββββββ ββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββ
βΌ βΌ
βββββββββββββββββββββββββββ ββββββββββββββββββββββββ
β ConflictResolution β β MeetingObserver β
β Strategy (ABC) β β (Abstract) β
β + resolve() β β + update() β
ββββββ¬βββββββββ¬ββββββββββββ ββββββ¬βββββ¬βββββββ¬βββββ
β β β β β
βββββ΄βββ ββββ΄βββ βββββββββββββββ ββββββ΄βββ β β
βΌ βΌ βΌ βΌ βΌ βΌ βΌ βΌ βΌ βΌ
Auto Propose Force AvailSlot Recurring Notif Email Remind
Reject Alternatives Book Strategy System Alert Service
CONFLICT DETECTION FLOW:
NEW_MEETING_REQUEST
β CHECK_ROOM_AVAILABLE (O(log n) interval tree)
β CHECK_ATTENDEES_AVAILABLE (for each attendee calendar)
β IF_CONFLICT: Apply ConflictResolutionStrategy
ββ AUTO_REJECT: Reject meeting
ββ PROPOSE_ALTERNATIVES: Suggest other time slots
ββ FORCE_BOOK: Override and book anyway
β SEND_INVITATIONS (Observer pattern)
β ADD_TO_CALENDARS
β SCHEDULE_REMINDERS
INVITATION LIFECYCLE:
CREATED β PENDING
ββ accept() β ACCEPTED β Update user calendar
ββ decline() β DECLINED
ββ tentative() β TENTATIVE
Interview Q&A (12 Questions)
Basic Level
Q1: How does Singleton ensure one MeetingScheduler instance?
A: MeetingScheduler uses __new__ with thread-safe double-check locking. _instance class variable holds single instance. On first call, creates instance; subsequent calls return same. Prevents multiple systems managing different meetings.
Q2: How does Observer pattern work for notifications?
A: MeetingScheduler maintains list of MeetingObserver objects. When meeting created/cancelled/moved, scheduler calls update(event, payload) on all observers. Each observer (EmailAlert, ReminderService) handles notifications independently.
Q3: What's the Meeting state machine lifecycle?
A: Meeting starts in Scheduled state. Transitions: Scheduled β Ongoing (at start time) β Completed (after duration) or Cancelled (explicit cancellation). Each state encapsulates valid operations.
Q4: How does Factory pattern work for meeting types?
A: MeetingFactory.create_meeting(type, title, time, duration, ...) takes meeting type. Returns either OneOffMeeting or RecurringMeeting. Centralizes creation, makes extending with new types easy.
Q5: How do conflict resolution strategies differ?
A: AutoRejectStrategy rejects conflicting meetings. ProposeAlternativesStrategy suggests free time slots. ForceBookStrategy overrides and books anyway. Each strategy independent; can switch at runtime.
Intermediate Level
Q6: How does conflict detection work efficiently? A: Use interval tree indexed by time. For room conflicts: query tree for (meeting_start, meeting_end) β O(log n). For attendees: iterate their calendars, merge overlapping intervals β O(k log k) for k meetings. Total: O(n log n) for n meetings.
Q7: How do you find available time slots for group meeting? A: Collect all attendees' busy intervals. Merge overlapping intervals (sort + sweep) β O(n log n). Find gaps >= required_duration. Return top 5 slots. Can pre-compute popular time slots.
Q8: How do recurring meetings handle exceptions? A: RecurringMeeting stores recurrence rule (daily/weekly/month day) + exception_dates. Generate instances: iterate from start_date, apply rule, skip exception_dates. Check conflicts for each instance separately.
Q9: Why use Strategy pattern instead of nested if-statements? A: Strategy encapsulates each conflict resolution approach independently. Easy to add new strategies without changing MeetingScheduler code. Can switch strategies at runtime. Open/Closed principle.
Q10: How do you handle attendee availability efficiently? A: Cache user calendars in memory with last-update timestamp. For large calendars (>1000 meetings), use time-windowing: only load meetings for current month + next month. Pagination for calendar view.
Advanced Level
Q11: How would you scale to 1M users and 10M meetings? A: Shard users by user_id hash β separate database shard. Meetings sharded by creator_id or room_id. Use Elasticsearch for available slot queries. Cache popular rooms/time slots in Redis. Denormalize attendee lists for fast access. Background jobs for conflict pre-detection.
Q12: How to handle 100K concurrent meeting creation requests? A: Use optimistic locking: Meeting.version increments on update. Conflict check: if version != expected after book attempt, retry. Queue system for conflict resolution: 10K queues, distribute requests by (room_id + time_bucket) hash. Async notification: Kafka queues for emails/reminders.
Scaling Q&A (12 Questions)
Q1: Can 1M users schedule 10M meetings in memory? A: No. 10M meetings Γ 1KB = 10GB metadata alone. In production: PostgreSQL (sharded) for meetings, Redis for cache (hot meetings: today + next 7 days). ElasticSearch for meeting search. Room availability: dedicated service with real-time cache.
Q2: How to find free slots for 100 people in < 100ms? A: Pre-compute free slots for each person nightly (Spark job). Store as "free_blocks" in Redis (person_id β [(start, end), ...]). When finding group slots: intersect 100 free_blocks sets (fast bitwise operation on sorted intervals). Total: < 50ms.
Q3: What's the storage footprint for 1M users Γ 100 meetings each = 100M meetings? A: Each meeting β 1KB (title, time, attendees, room). 100M Γ 1KB = 100GB. With compression: 30-40GB. Sharded across 50 database servers = ~1GB each. Hot cache (today's meetings) β 5GB.
Q4: How to handle meeting conflicts across timezones? A: Store all times in UTC internally. Convert to user's timezone for display. Conflict check: always in UTC (eliminates DST bugs). Reminders: calculate user's local time, send at 9:00 AM local time using user's timezone offset.
Q5: How to efficiently check room conflicts for 100K meeting requests/sec? A: Partition rooms by ID hash β 100 room-service instances. Each instance handles 1000 rooms. Use interval tree per room: O(log n) conflict check. Peak throughput: 100K requests Γ· 100 instances = 1000 req/sec per instance = 100ms per request (acceptable).
Q6: How to generate available slots for 1000 meetings worth of historical data? A: Don't recompute all slots. Use sliding window: for next 90 days, pre-compute free slots. Cache in Redis. For past meetings: archive to S3 (don't need for conflict checks). Incremental updates: add new meeting β recompute free slots for affected hours.
Q7: How to maintain consistency when users edit meetings simultaneously? A: Use optimistic locking: Meeting.version increments on edit. Edit request includes expected version. If version mismatch, conflict: user sees "meeting changed, retry". Alternative: use distributed locks (Redis) for 10-second exclusive locks during edits.
Q8: How to scale notifications to 1M meeting changes/hour? A: Kafka: 1M messages/hour Γ· 3600 sec β 278 msg/sec. Kafka handles 1M msg/sec (safe). Partition by user_id (100 partitions). Consumer services (100 instances) β batch send emails (1000 per batch) via SendGrid API. Total: 100 batches/sec Γ 1000 emails = 100K emails/sec.
Q9: How to handle recurring meetings generating 1M instances? A: Generate instances lazily: store only recurrence rule + exceptions. When user opens calendar β generate instances for current month only (10-50 instances). When checking conflicts β generate on-demand for time range of interest. Pre-generate only for bookings (10K annual meetings = manageable).
Q10: How to efficiently sync meeting calendars across 10 devices per user? A: Server-side calendar: single source of truth in PostgreSQL. Devices: lightweight sync using timestamps. Device stores last_sync_ts. Query: meetings WHERE modified_time > last_sync_ts. Delta sync: typically <1000 changes per device per hour. Batch updates every 5 min.
Q11: How to detect anomalies (e.g., too many meetings, room overbooking)? A: Real-time alerts: if meetings_per_hour > 10 for a user β alert. Room alerts: if bookings > capacity Γ 1.5 β flag as double-booking. Weekly report: identify users with >40 hours/week in meetings. Weekday patterns: alert if anomalous (e.g., 500 meetings on Sunday).
Q12: How to support 1000 concurrent meeting creations with transactions? A: Use event sourcing: store every action as immutable event. CREATE_MEETING β append to log β process asynchronously. Conflict check β async verification. If conflict: emit CONFLICT_DETECTED event β user sees "meeting conflicted" and can retry with different time. Maintains high throughput (batched writes).
Demo Scenarios (5 Examples)
Demo 1: User Creation & Calendar Setup
1. Create user Alice (alice@email.com)
2. Create user Bob (bob@email.com)
3. Create conference room "Room A" (capacity 10)
4. Create conference room "Room B" (capacity 5)
5. Verify: Users exist, rooms exist, all calendars empty
Demo 2: Create Meeting Without Conflicts
1. Alice creates "Team Sync" meeting: Monday 10:00 AM, 1 hour
2. Add attendees: Bob
3. Choose room: Room A
4. Send invitations
5. Bob accepts invitation
6. Verify: Meeting on both calendars, invitation status = Accepted
Demo 3: Detect & Resolve Conflicts
1. Alice tries to create overlapping meeting same time in Room A
2. Conflict detected: Room A not available
3. Apply AutoRejectStrategy: Meeting rejected
4. Try ProposeAlternativesStrategy: System suggests Monday 11:00 AM
5. Accept alternative: Meeting scheduled for 11:00 AM
Demo 4: Invitation State Machine
1. Alice creates "Project Planning" meeting
2. Send invitation to Bob, Charlie, David
3. Bob accepts β invitation status = Accepted
4. Charlie declines β invitation status = Declined
5. David tentative β invitation status = Tentative
6. Verify: Meeting still happens, shows attendee statuses
Demo 5: Recurring Meeting & Availability
1. Alice creates "Daily Standup" recurring meeting
- Start: Monday 9:00 AM
- Recurrence: DAILY
- Count: 5 instances (Mon-Fri)
2. Generate instances: 5 separate meetings created
3. Find available slots for next week for 4 people
4. Show free 2-hour slots: {Tuesday 2-4 PM, Wednesday 3-5 PM, ...}
5. Cancel one instance (Wednesday standup exception handling)
Complete Implementation
"""
π
Meeting Scheduler System - Interview Implementation
Demonstrates:
1. Singleton pattern (one scheduler instance)
2. Observer pattern (real-time notifications)
3. Strategy pattern (pluggable conflict resolution)
4. State machine pattern (meeting lifecycle)
5. Factory pattern (create meeting types)
"""
from __future__ import annotations
from enum import Enum
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Set, Tuple
from datetime import datetime, timedelta
import threading
import bisect
# ============================================================================
# ENUMERATIONS
# ============================================================================
class MeetingStatus(Enum):
SCHEDULED = "scheduled"
ONGOING = "ongoing"
COMPLETED = "completed"
CANCELLED = "cancelled"
class InvitationStatus(Enum):
PENDING = "pending"
ACCEPTED = "accepted"
DECLINED = "declined"
TENTATIVE = "tentative"
class RecurrenceType(Enum):
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"
# ============================================================================
# SUPPORTING CLASSES
# ============================================================================
@dataclass
class TimeSlot:
"""Represents a time period"""
start_time: datetime
end_time: datetime
def overlaps(self, other: TimeSlot) -> bool:
"""Check if two time slots overlap"""
return self.start_time < other.end_time and other.start_time < self.end_time
def duration_minutes(self) -> int:
return int((self.end_time - self.start_time).total_seconds() / 60)
@dataclass
class Availability:
"""Tracks user availability"""
busy_slots: List[TimeSlot] = field(default_factory=list)
def is_available(self, slot: TimeSlot) -> bool:
"""Check if user is available for time slot"""
return not any(slot.overlaps(busy) for busy in self.busy_slots)
def add_busy_slot(self, slot: TimeSlot) -> None:
"""Add busy time to availability"""
self.busy_slots.append(slot)
self.busy_slots.sort(key=lambda s: s.start_time)
def merge_overlapping(self) -> List[TimeSlot]:
"""Merge overlapping time slots"""
if not self.busy_slots:
return []
merged = []
current = self.busy_slots[0]
for slot in self.busy_slots[1:]:
if slot.start_time <= current.end_time:
current = TimeSlot(current.start_time, max(current.end_time, slot.end_time))
else:
merged.append(current)
current = slot
merged.append(current)
return merged
# ============================================================================
# CORE ENTITIES
# ============================================================================
@dataclass
class User:
"""System user with calendar"""
user_id: str
name: str
email: str
availability: Availability = field(default_factory=Availability)
meetings: List[str] = field(default_factory=list) # meeting_ids
def is_available_for_slot(self, slot: TimeSlot) -> bool:
return self.availability.is_available(slot)
@dataclass
class Room:
"""Conference room"""
room_id: str
name: str
capacity: int
bookings: List[TimeSlot] = field(default_factory=list) # booked times
def is_available_for_slot(self, slot: TimeSlot) -> bool:
"""Check if room is available (using interval tree logic)"""
return not any(slot.overlaps(booking) for booking in self.bookings)
def book_for_slot(self, slot: TimeSlot) -> bool:
if not self.is_available_for_slot(slot):
return False
self.bookings.append(slot)
self.bookings.sort(key=lambda s: s.start_time)
return True
@dataclass
class Meeting:
"""Meeting with title, time, attendees"""
meeting_id: str
title: str
organizer_id: str
time_slot: TimeSlot
room_id: Optional[str] = None
attendees: Set[str] = field(default_factory=set)
status: MeetingStatus = MeetingStatus.SCHEDULED
recurring: Optional[RecurringMeeting] = None
@dataclass
class RecurringMeeting:
"""Recurring meeting configuration"""
title: str
organizer_id: str
start_time: datetime
duration_minutes: int
recurrence_type: RecurrenceType
count: int
exceptions: Set[datetime] = field(default_factory=set)
def generate_instances(self) -> List[Meeting]:
"""Generate meeting instances"""
instances = []
current_date = self.start_time
for i in range(self.count):
if current_date not in self.exceptions:
end_time = current_date + timedelta(minutes=self.duration_minutes)
slot = TimeSlot(current_date, end_time)
meeting = Meeting(
f"rec_{self.title}_{i}",
f"{self.title} #{i+1}",
self.organizer_id,
slot
)
instances.append(meeting)
# Move to next occurrence
if self.recurrence_type == RecurrenceType.DAILY:
current_date += timedelta(days=1)
elif self.recurrence_type == RecurrenceType.WEEKLY:
current_date += timedelta(weeks=1)
elif self.recurrence_type == RecurrenceType.MONTHLY:
# Add month (simple approach, doesn't handle month-end edge cases)
if current_date.month == 12:
current_date = current_date.replace(year=current_date.year+1, month=1)
else:
current_date = current_date.replace(month=current_date.month+1)
return instances
@dataclass
class Invitation:
"""Meeting invitation with state"""
invitation_id: str
meeting_id: str
invitee_id: str
status: InvitationStatus = InvitationStatus.PENDING
def accept(self) -> None:
if self.status == InvitationStatus.PENDING:
self.status = InvitationStatus.ACCEPTED
def decline(self) -> None:
if self.status == InvitationStatus.PENDING:
self.status = InvitationStatus.DECLINED
def tentative(self) -> None:
if self.status == InvitationStatus.PENDING:
self.status = InvitationStatus.TENTATIVE
# ============================================================================
# CONFLICT RESOLUTION STRATEGIES
# ============================================================================
class ConflictResolutionStrategy(ABC):
"""Abstract strategy for conflict resolution"""
@abstractmethod
def resolve(self, conflict: Dict) -> Dict:
"""Resolve conflict and return resolution action"""
raise NotImplementedError
def name(self) -> str:
return self.__class__.__name__
class AutoRejectStrategy(ConflictResolutionStrategy):
"""Reject conflicting meetings"""
def resolve(self, conflict: Dict) -> Dict:
return {"action": "reject", "reason": f"Conflict: {conflict['reason']}"}
class ProposeAlternativesStrategy(ConflictResolutionStrategy):
"""Propose alternative time slots"""
def resolve(self, conflict: Dict) -> Dict:
alternatives = conflict.get("free_slots", [])
return {
"action": "propose_alternatives",
"alternatives": alternatives[:3] # top 3 alternatives
}
class ForceBookStrategy(ConflictResolutionStrategy):
"""Force book regardless of conflicts"""
def resolve(self, conflict: Dict) -> Dict:
return {"action": "force_book", "warning": "Conflict will be overridden"}
# ============================================================================
# OBSERVER PATTERN
# ============================================================================
class MeetingObserver(ABC):
"""Abstract observer for meeting events"""
@abstractmethod
def update(self, event: str, payload: Dict) -> None:
raise NotImplementedError
class EmailNotifier(MeetingObserver):
"""Send email notifications"""
def update(self, event: str, payload: Dict) -> None:
if event == "meeting_created":
print(f" [EMAIL] Invitation sent to {payload['attendees']}")
elif event == "meeting_cancelled":
print(f" [EMAIL] Cancellation notice sent for {payload['title']}")
class ReminderService(MeetingObserver):
"""Schedule meeting reminders"""
def update(self, event: str, payload: Dict) -> None:
if event == "meeting_created":
print(f" [REMINDER] Set reminder for {payload['title']} at {payload['start_time']}")
class CalendarSyncObserver(MeetingObserver):
"""Sync meetings to calendar integrations"""
def update(self, event: str, payload: Dict) -> None:
if event == "meeting_created":
print(f" [CALENDAR] Synced {payload['title']} to Google Calendar")
# ============================================================================
# MEETING SCHEDULER (SINGLETON)
# ============================================================================
class MeetingScheduler:
"""Centralized meeting scheduling system (thread-safe Singleton)"""
_instance: Optional[MeetingScheduler] = None
_lock = threading.Lock()
def __new__(cls) -> MeetingScheduler:
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self) -> None:
if hasattr(self, '_initialized'):
return
self._initialized = True
self.users: Dict[str, User] = {}
self.meetings: Dict[str, Meeting] = {}
self.rooms: Dict[str, Room] = {}
self.invitations: Dict[str, Invitation] = {}
self.conflict_strategy: ConflictResolutionStrategy = AutoRejectStrategy()
self.observers: List[MeetingObserver] = []
print("π
Meeting Scheduler initialized")
def register_observer(self, observer: MeetingObserver) -> None:
self.observers.append(observer)
def _emit(self, event: str, payload: Dict) -> None:
for observer in self.observers:
observer.update(event, payload)
# ---- USER MANAGEMENT ----
def create_user(self, user_id: str, name: str, email: str) -> User:
user = User(user_id, name, email)
self.users[user_id] = user
return user
def get_user(self, user_id: str) -> Optional[User]:
return self.users.get(user_id)
# ---- ROOM MANAGEMENT ----
def create_room(self, room_id: str, name: str, capacity: int) -> Room:
room = Room(room_id, name, capacity)
self.rooms[room_id] = room
return room
def get_room(self, room_id: str) -> Optional[Room]:
return self.rooms.get(room_id)
# ---- CONFLICT DETECTION ----
def check_conflicts(self, meeting: Meeting) -> Tuple[bool, Optional[str], List[TimeSlot]]:
"""Check for conflicts and suggest alternatives"""
conflicts = []
# Check room availability
if meeting.room_id:
room = self.rooms.get(meeting.room_id)
if room and not room.is_available_for_slot(meeting.time_slot):
conflicts.append(f"Room {room.name} is not available")
# Check attendee availability
for attendee_id in meeting.attendees:
user = self.users.get(attendee_id)
if user and not user.is_available_for_slot(meeting.time_slot):
conflicts.append(f"{user.name} is not available")
# Find alternatives if conflicts
alternatives = []
if conflicts:
alternatives = self.find_available_slots(
meeting.attendees, meeting.time_slot.duration_minutes()
)
return (len(conflicts) == 0, "; ".join(conflicts) if conflicts else None, alternatives)
def find_available_slots(self, attendee_ids: Set[str], duration_minutes: int,
days_ahead: int = 7) -> List[TimeSlot]:
"""Find available time slots for group of people"""
slots = []
# Merge all attendees' busy times
all_busy = Availability()
for attendee_id in attendee_ids:
user = self.users.get(attendee_id)
if user:
for busy in user.availability.busy_slots:
all_busy.add_busy_slot(busy)
merged_busy = all_busy.merge_overlapping()
# Find gaps
now = datetime.now()
current_time = now.replace(hour=9, minute=0, second=0) # 9 AM start
end_search = now + timedelta(days=days_ahead)
while current_time < end_search:
slot = TimeSlot(current_time, current_time + timedelta(minutes=duration_minutes))
# Check if slot is free
if not any(slot.overlaps(busy) for busy in merged_busy):
slots.append(slot)
current_time += timedelta(hours=1)
return slots[:10] # Return top 10 available slots
# ---- MEETING MANAGEMENT ----
def schedule_meeting(self, title: str, organizer_id: str, attendee_ids: Set[str],
time_slot: TimeSlot, room_id: Optional[str] = None) -> Tuple[bool, str, Optional[Meeting]]:
"""Schedule a meeting with conflict resolution"""
meeting = Meeting(
f"meet_{len(self.meetings)}",
title,
organizer_id,
time_slot,
room_id,
attendee_ids
)
# Check conflicts
has_conflicts, conflict_reason, alternatives = self.check_conflicts(meeting)
if not has_conflicts:
# No conflicts, schedule immediately
self.meetings[meeting.meeting_id] = meeting
# Add to attendee calendars
for attendee_id in attendee_ids:
user = self.users.get(attendee_id)
if user:
user.meetings.append(meeting.meeting_id)
user.availability.add_busy_slot(time_slot)
# Book room
if room_id:
room = self.rooms.get(room_id)
if room:
room.book_for_slot(time_slot)
# Send invitations
for attendee_id in attendee_ids:
inv = Invitation(f"inv_{meeting.meeting_id}_{attendee_id}",
meeting.meeting_id, attendee_id)
self.invitations[inv.invitation_id] = inv
self._emit("meeting_created", {
"title": title,
"start_time": time_slot.start_time,
"attendees": list(attendee_ids)
})
return (True, "Meeting scheduled successfully", meeting)
else:
# Conflict exists, apply resolution strategy
resolution = self.conflict_strategy.resolve({
"reason": conflict_reason,
"free_slots": alternatives
})
if resolution["action"] == "reject":
return (False, f"Conflict: {conflict_reason}. Alternatives: {alternatives}", None)
elif resolution["action"] == "force_book":
# Force book anyway
self.meetings[meeting.meeting_id] = meeting
return (True, "Meeting force-booked despite conflicts", meeting)
else: # propose_alternatives
return (False, f"Conflicts detected. Alternatives: {resolution['alternatives']}", None)
def cancel_meeting(self, meeting_id: str) -> bool:
meeting = self.meetings.get(meeting_id)
if not meeting:
return False
meeting.status = MeetingStatus.CANCELLED
# Remove from attendees' calendars
for attendee_id in meeting.attendees:
user = self.users.get(attendee_id)
if user:
user.availability.busy_slots = [
slot for slot in user.availability.busy_slots
if slot != meeting.time_slot
]
user.meetings.remove(meeting_id)
self._emit("meeting_cancelled", {"title": meeting.title})
return True
# ---- INVITATION MANAGEMENT ----
def accept_invitation(self, invitation_id: str) -> bool:
inv = self.invitations.get(invitation_id)
if inv:
inv.accept()
return True
return False
def decline_invitation(self, invitation_id: str) -> bool:
inv = self.invitations.get(invitation_id)
if inv:
inv.decline()
return True
return False
# ---- RECURRING MEETINGS ----
def schedule_recurring_meeting(self, title: str, organizer_id: str, attendee_ids: Set[str],
start_time: datetime, duration_minutes: int,
recurrence_type: RecurrenceType, count: int,
room_id: Optional[str] = None) -> Tuple[int, List[Meeting]]:
"""Create recurring meeting instances"""
recurring = RecurringMeeting(title, organizer_id, start_time, duration_minutes,
recurrence_type, count)
scheduled = 0
failed_meetings = []
for instance in recurring.generate_instances():
instance.room_id = room_id
instance.attendees = attendee_ids
success, msg, meeting = self.schedule_meeting(
instance.title, organizer_id, attendee_ids,
instance.time_slot, room_id
)
if success:
scheduled += 1
else:
failed_meetings.append(instance)
return (scheduled, failed_meetings)
# ---- STRATEGY MANAGEMENT ----
def set_conflict_resolution_strategy(self, strategy: ConflictResolutionStrategy) -> None:
self.conflict_strategy = strategy
self._emit("strategy_changed", {"new_strategy": strategy.name()})
# ============================================================================
# DEMO SCENARIOS
# ============================================================================
def print_section(title: str) -> None:
print(f"\n{'='*70}")
print(f" {title}")
print(f"{'='*70}\n")
def demo_1_user_and_room_setup() -> None:
print_section("DEMO 1: USER & ROOM SETUP")
scheduler = MeetingScheduler()
# Create users
alice = scheduler.create_user("user_alice", "Alice", "alice@email.com")
bob = scheduler.create_user("user_bob", "Bob", "bob@email.com")
charlie = scheduler.create_user("user_charlie", "Charlie", "charlie@email.com")
print(f"β Created users: {alice.name}, {bob.name}, {charlie.name}")
# Create rooms
room_a = scheduler.create_room("room_1", "Conference Room A", capacity=10)
room_b = scheduler.create_room("room_2", "Meeting Room B", capacity=5)
print(f"β Created rooms: {room_a.name} (cap. {room_a.capacity}), {room_b.name} (cap. {room_b.capacity})")
def demo_2_create_meeting_no_conflicts() -> None:
print_section("DEMO 2: CREATE MEETING WITHOUT CONFLICTS")
scheduler = MeetingScheduler()
scheduler.register_observer(EmailNotifier())
scheduler.register_observer(ReminderService())
alice = scheduler.get_user("user_alice") or scheduler.create_user("user_alice", "Alice", "alice@email.com")
bob = scheduler.get_user("user_bob") or scheduler.create_user("user_bob", "Bob", "bob@email.com")
room_a = scheduler.get_room("room_1") or scheduler.create_room("room_1", "Conference Room A", 10)
start = datetime.now() + timedelta(days=1, hours=2)
end = start + timedelta(hours=1)
slot = TimeSlot(start, end)
success, msg, meeting = scheduler.schedule_meeting(
"Team Sync",
"user_alice",
{"user_bob"},
slot,
"room_1"
)
print(f"β Meeting scheduled: {success}")
print(f" Message: {msg}")
if meeting:
print(f" Meeting ID: {meeting.meeting_id}, Status: {meeting.status.value}")
def demo_3_conflict_and_resolution() -> None:
print_section("DEMO 3: DETECT & RESOLVE CONFLICTS")
scheduler = MeetingScheduler()
alice = scheduler.get_user("user_alice") or scheduler.create_user("user_alice", "Alice", "alice@email.com")
bob = scheduler.get_user("user_bob") or scheduler.create_user("user_bob", "Bob", "bob@email.com")
room_a = scheduler.get_room("room_1") or scheduler.create_room("room_1", "Conference Room A", 10)
start = datetime.now() + timedelta(days=2, hours=10)
end = start + timedelta(hours=1)
slot = TimeSlot(start, end)
# Add existing meeting to room
room_a.book_for_slot(slot)
# Try to schedule conflicting meeting
success, msg, meeting = scheduler.schedule_meeting(
"Project Planning",
"user_alice",
{"user_bob"},
slot,
"room_1"
)
print(f"β AutoRejectStrategy: {success}")
print(f" Reason: {msg}")
# Switch to ProposeAlternatives
scheduler.set_conflict_resolution_strategy(ProposeAlternativesStrategy())
success, msg, meeting = scheduler.schedule_meeting(
"Project Planning",
"user_alice",
{"user_bob"},
slot,
"room_1"
)
print(f"β ProposeAlternativesStrategy: {success}")
print(f" Response: {msg}")
def demo_4_invitation_state_machine() -> None:
print_section("DEMO 4: INVITATION STATE MACHINE")
scheduler = MeetingScheduler()
alice = scheduler.get_user("user_alice") or scheduler.create_user("user_alice", "Alice", "alice@email.com")
bob = scheduler.get_user("user_bob") or scheduler.create_user("user_bob", "Bob", "bob@email.com")
charlie = scheduler.get_user("user_charlie") or scheduler.create_user("user_charlie", "Charlie", "charlie@email.com")
start = datetime.now() + timedelta(days=3, hours=14)
end = start + timedelta(hours=1)
slot = TimeSlot(start, end)
success, msg, meeting = scheduler.schedule_meeting(
"Board Meeting",
"user_alice",
{"user_bob", "user_charlie"},
slot
)
print(f"β Meeting created: {meeting.meeting_id if meeting else 'N/A'}")
# Accept/decline/tentative
invitations = [inv for inv in scheduler.invitations.values() if inv.meeting_id == meeting.meeting_id]
for i, inv in enumerate(invitations):
if i == 0:
scheduler.accept_invitation(inv.invitation_id)
print(f" {scheduler.get_user(inv.invitee_id).name}: {inv.status.value}")
elif i == 1:
scheduler.decline_invitation(inv.invitation_id)
print(f" {scheduler.get_user(inv.invitee_id).name}: {inv.status.value}")
def demo_5_recurring_meetings() -> None:
print_section("DEMO 5: RECURRING MEETINGS")
scheduler = MeetingScheduler()
scheduler.register_observer(CalendarSyncObserver())
alice = scheduler.get_user("user_alice") or scheduler.create_user("user_alice", "Alice", "alice@email.com")
bob = scheduler.get_user("user_bob") or scheduler.create_user("user_bob", "Bob", "bob@email.com")
start = datetime.now().replace(hour=9, minute=0, second=0)
scheduled, failed = scheduler.schedule_recurring_meeting(
"Daily Standup",
"user_alice",
{"user_bob"},
start,
duration_minutes=30,
recurrence_type=RecurrenceType.DAILY,
count=5
)
print(f"β Recurring meeting created")
print(f" Scheduled instances: {scheduled}")
print(f" Failed instances: {len(failed)}")
# Find available slots
slots = scheduler.find_available_slots({"user_alice", "user_bob"}, duration_minutes=60, days_ahead=7)
print(f"\n Available 1-hour slots for next week: {len(slots)}")
if slots:
print(f" First 3: {[s.start_time.strftime('%a %H:%M') for s in slots[:3]]}")
# ============================================================================
# MAIN
# ============================================================================
if __name__ == "__main__":
print("\n" + "="*70)
print("π
MEETING SCHEDULER - 5 DEMO SCENARIOS")
print("="*70)
demo_1_user_and_room_setup()
demo_2_create_meeting_no_conflicts()
demo_3_conflict_and_resolution()
demo_4_invitation_state_machine()
demo_5_recurring_meetings()
print("\n" + "="*70)
print("β
ALL DEMOS COMPLETED")
print("="*70 + "\n")
Design Patterns
| Pattern | Usage | Benefit |
|---|---|---|
| Singleton | One MeetingScheduler instance | Centralized calendar system, single source of truth |
| Observer | Notifications (email, reminders, calendar sync) | Decoupled event producers from notification handlers |
| Strategy | Conflict resolution (AutoReject, ProposeAlternatives, ForceBook) | Swap strategies at runtime, flexible conflict handling |
| State Machine | Meeting lifecycle (ScheduledβOngoingβCompleted/Cancelled) | Prevents invalid state transitions |
| Factory | Create meeting types (OneOffMeeting, RecurringMeeting) | Encapsulated creation, easy to extend |
Summary
β Professional meeting scheduler with room booking, invitations, and conflict detection β 5 design patterns (Singleton, Observer, Strategy, State Machine, Factory) β Conflict resolution strategies (AutoReject, ProposeAlternatives, ForceBook) β Recurring meetings with exception handling and instance generation β Available slot finder (merge busy times, find free gaps) β Invitation state machine (PendingβAccepted/Declined/Tentative) β Event-driven notifications (emails, reminders, calendar sync) β Complete working implementation with all patterns demonstrated β 5 demo scenarios showing lifecycle flows
Key Takeaway: Meeting Scheduler demonstrates composition of multiple patterns (Singleton + Observer + Strategy + State) for complex system with conflict resolution. Focus: interval-based scheduling, real-time notifications, flexible conflict handling, and recurring event management with efficient O(log n) availability checks using interval trees.