Skip to main content

Design a Google Calendar Scheduler (with Complex Recurrence Rules)

Problem Statement

Design a Google Calendar Scheduler. The scheduler must support creating user calendars, booking meetings (events), checking for scheduling conflicts (time overlaps) dynamically, and handling complex recurrence rules (like daily occurrences, or weekly repetitions on specific days) by expanding them into concrete event instances.

Asked In Companies
Google Microsoft Uber

Design Decisions & Patterns Used

Representing calendar entries requires separating the **abstract event definition** (the rule) from its **concrete occurrences** (the event instances). When booking a recurring event, we must evaluate the rule and generate instances over a target date range. We can then check for conflicts by comparing these generated time ranges against existing instances in the user's calendar.

We will utilize the following Design Patterns:

  • Strategy Pattern: Defining interchangeable algorithms (rules) to calculate event occurrences (e.g., Daily vs. Weekly recurrence rules).
  • Factory Pattern: Instantiating recurrence rules dynamically based on rule properties.

Functional Requirements

  • Create events with a start time, duration, and optional recurrence rules.
  • Support one-time events and recurring events (e.g., Daily, or Weekly on specific days like Monday and Wednesday).
  • Expand recurring events into concrete occurrence instances over a target date range.
  • Check for scheduling conflicts (overlapping times) before committing a booking.

Objects Required

  • RecurrenceRule (Interface defining occurrence calculation contracts)
  • NoRecurrence, DailyRecurrence, WeeklyRecurrence (Concrete rule algorithms)
  • Event (Abstract definition representing the booked meeting rules)
  • EventInstance (Value object tracking concrete start and end times)
  • Calendar (User calendar holding booked events and checking overlaps)

RecurrenceRule Interface & Implementations

The RecurrenceRule interface defines the contract for generating occurrences. Applying the **Strategy Pattern** allows adding new recurrence behaviors without altering the core scheduler.


import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

public interface RecurrenceRule {
    List<LocalDateTime> generateOccurrences(LocalDateTime start, LocalDateTime rangeEnd);
}

Let's implement the concrete recurrence algorithms:


public class NoRecurrence implements RecurrenceRule {
    @Override
    public List<LocalDateTime> generateOccurrences(LocalDateTime start, LocalDateTime rangeEnd) {
        if (start.isAfter(rangeEnd)) return Collections.emptyList();
        return Collections.singletonList(start);
    }
}

The NoRecurrence rule represents a one-time event, returning only the start time.


public class DailyRecurrence implements RecurrenceRule {
    @Override
    public List<LocalDateTime> generateOccurrences(LocalDateTime start, LocalDateTime rangeEnd) {
        List<LocalDateTime> occurrences = new ArrayList<>();
        LocalDateTime current = start;
        while (!current.isAfter(rangeEnd)) {
            occurrences.add(current);
            current = current.plusDays(1); // Increment by one day
        }
        return occurrences;
    }
}

The DailyRecurrence rule increments the timestamp by one day sequentially until it reaches the range boundary.


import java.time.DayOfWeek;

public class WeeklyRecurrence implements RecurrenceRule {
    private final Set<DayOfWeek> daysOfWeek;

    public WeeklyRecurrence(Set<DayOfWeek> daysOfWeek) {
        this.daysOfWeek = daysOfWeek;
    }

    @Override
    public List<LocalDateTime> generateOccurrences(LocalDateTime start, LocalDateTime rangeEnd) {
        List<LocalDateTime> occurrences = new ArrayList<>();
        LocalDateTime current = start;
        while (!current.isAfter(rangeEnd)) {
            if (daysOfWeek.contains(current.getDayOfWeek())) {
                occurrences.add(current);
            }
            current = current.plusDays(1);
        }
        return occurrences;
    }
}

The WeeklyRecurrence rule checks if the current day of the week matches the configured set (e.g., Monday and Wednesday) before adding the occurrence.


Event & EventInstance Classes

The Event represents the template definition of a meeting, while the EventInstance tracks concrete occurrence intervals.


import java.time.LocalDateTime;

public class Event {
    private final String id;
    private final String title;
    private final LocalDateTime start;
    private final int durationMinutes;
    private final RecurrenceRule recurrenceRule;

    public Event(String id, String title, LocalDateTime start, int durationMinutes, RecurrenceRule recurrenceRule) {
        this.id = id;
        this.title = title;
        this.start = start;
        this.durationMinutes = durationMinutes;
        this.recurrenceRule = recurrenceRule;
    }

    public String getId() { return id; }
    public String getTitle() { return title; }
    public LocalDateTime getStart() { return start; }
    public int getDurationMinutes() { return durationMinutes; }
    public RecurrenceRule getRecurrenceRule() { return recurrenceRule; }
}

The constructor configures the meeting properties. Let's define the EventInstance class:


import java.time.LocalDateTime;

public class EventInstance {
    private final String eventId;
    private final String title;
    private final LocalDateTime startTime;
    private final LocalDateTime endTime;

    public EventInstance(String eventId, String title, LocalDateTime startTime, int durationMinutes) {
        this.eventId = eventId;
        this.title = title;
        this.startTime = startTime;
        this.endTime = startTime.plusMinutes(durationMinutes);
    }

    public boolean overlapsWith(EventInstance other) {
        // Return true if intervals intersect: (StartA < EndB) AND (EndA > StartB)
        return this.startTime.isBefore(other.endTime) && this.endTime.isAfter(other.startTime);
    }

    public String getEventId() { return eventId; }
    public String getTitle() { return title; }
    public LocalDateTime getStartTime() { return startTime; }
    public LocalDateTime getEndTime() { return endTime; }
}

The constructor calculates the end time based on the duration. overlapsWith() checks for conflicts between time intervals.


Calendar Class

The Calendar class stores events, expands recurrence rules, and checks for scheduling conflicts before booking.


import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class Calendar {
    private final List<Event> events;
    private final List<EventInstance> bookedInstances;

    public Calendar() {
        this.events = new ArrayList<>();
        this.bookedInstances = new ArrayList<>();
    }

    public synchronized void bookEvent(Event event, LocalDateTime rangeEnd) {
        // Expand the event's recurrence rule to generate instances
        List<LocalDateTime> startTimes = event.getRecurrenceRule().generateOccurrences(event.getStart(), rangeEnd);
        List<EventInstance> incomingInstances = new ArrayList<>();

        for (LocalDateTime time : startTimes) {
            incomingInstances.add(new EventInstance(event.getId(), event.getTitle(), time, event.getDurationMinutes()));
        }

        // Check for conflicts against existing instances
        for (EventInstance incoming : incomingInstances) {
            for (EventInstance existing : bookedInstances) {
                if (incoming.overlapsWith(existing)) {
                    throw new IllegalStateException("Scheduling Conflict: '" + incoming.getTitle() + "' at " + 
                            incoming.getStartTime() + " overlaps with '" + existing.getTitle() + "' at " + existing.getStartTime());
                }
            }
        }

        // Commit the booking if no conflicts are found
        events.add(event);
        bookedInstances.addAll(incomingInstances);
        System.out.println("Booked Event: '" + event.getTitle() + "' (" + incomingInstances.size() + " instances)");
    }

    public synchronized List<EventInstance> getBookedInstances() {
        return new ArrayList<>(bookedInstances);
    }
}

Here is an explanation of the core operations in the Calendar class:

  • The constructor initializes lists to store abstract events and concrete instances.
  • bookEvent() uses the event's recurrence rule to generate occurrences. It builds `EventInstance` instances and checks for conflicts against existing bookings. If a conflict is found, it rolls back and throws an exception; otherwise, it commits the booking.

Main Driver Class

This class tests our calendar scheduler. It books one-time and recurring events, validates conflict detection, and displays generated instances.


import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.util.EnumSet;

public class Main {
    public static void main(String[] args) {
        Calendar calendar = new Calendar();
        LocalDateTime rangeLimit = LocalDateTime.of(2026, 6, 14, 23, 59); // 1-week limit

        System.out.println("==========================================");
        System.out.println("Scenario 1: Booking Standup Meeting (Daily Recurrence)");
        System.out.println("==========================================");

        LocalDateTime standupStart = LocalDateTime.of(2026, 6, 8, 9, 0); // Monday 9:00 AM
        Event dailyStandup = new Event("E1", "Daily Standup", standupStart, 30, new DailyRecurrence());
        calendar.bookEvent(dailyStandup, rangeLimit);

        System.out.println("\n==========================================");
        System.out.println("Scenario 2: Booking One-time Event (No Conflict)");
        System.out.println("==========================================");

        // One-time meeting on Monday 10:00 AM (No conflict with 9:00 AM Standup)
        LocalDateTime meetingStart = LocalDateTime.of(2026, 6, 8, 10, 0);
        Event syncMeeting = new Event("E2", "Design Sync", meetingStart, 60, new NoRecurrence());
        calendar.bookEvent(syncMeeting, rangeLimit);

        System.out.println("\n==========================================");
        System.out.println("Scenario 3: Booking Conflicting Event");
        System.out.println("==========================================");

        // Try booking a meeting that overlaps with Wednesday's Standup (9:15 AM - 9:45 AM)
        LocalDateTime conflictStart = LocalDateTime.of(2026, 6, 10, 9, 15);
        Event badMeeting = new Event("E3", "Ad-hoc Call", conflictStart, 30, new NoRecurrence());

        try {
            calendar.bookEvent(badMeeting, rangeLimit);
        } catch (Exception e) {
            System.out.println("Caught Expected Exception:\n" + e.getMessage());
        }

        System.out.println("\n==========================================");
        System.out.println("Print Booked Instances for June 8, 2026");
        System.out.println("==========================================");
        for (EventInstance instance : calendar.getBookedInstances()) {
            if (instance.getStartTime().getDayOfMonth() == 8) {
                System.out.println("Meeting: " + instance.getTitle() + " | " + 
                        instance.getStartTime().toLocalTime() + " - " + instance.getEndTime().toLocalTime());
            }
        }
    }
}

The main() driver configures recurrence ranges, schedules daily and one-time events, verifies conflict checks, and prints the scheduled instances to confirm correct execution.


Also See

Comments

Popular posts from this blog

Designing a Parking Lot - Low Level Design

Problem Statement Design a parking lot that can handle vehicles entering and leaving while managing parking across multiple floors. Each vehicle should be assigned a suitable parking spot based on its type, and the spot should be freed once the vehicle exits. The design should also support generating a ticket at entry and optionally calculating the parking fee based on the duration of stay. Asked In Companies Amazon Google Microsoft Uber Walmart Flipkart Meta PayPal Oracle Salesforce Adobe Apple Intuit LinkedIn Atlassian Functional Requirements The design should support multiple vehicle types such as bikes, cars, and trucks A vehicle must be assigned a parking spot compatible with its type A parking spot cannot be assigned to more than one vehicle at a time The parking lot should support multiple levels (floors) The design should search and allocate an availa...

Most Frequently Asked Low Level Design(LLD) Interview Questions

Below are the curated list of most commonly asked Low Level Design (LLD) interview problems. Each problem includes a short description and a link to the complete solution with code and class diagrams. Design Parking Lot System The system should handle parking for different vehicle types such as bikes, cars, and trucks. It should manage slot allocation, availability tracking, and entry/exit flow. The design also ensures efficient usage of parking space under varying load conditions. View Solution Design Elevator / Lift System The system should support multiple elevators operating across floors with request handling logic. It focuses on scheduling algorithms to minimize wait time and optimize movement. It also manages direction control and concurrent floor requests. View Solution Design Movie Ticket Booking System The system should allow users to browse movies, select shows, and book seats. It handles seat ...

Software Design Patterns for LLD Interviews: A Complete Guide

Software Design Patterns for LLD Interviews: A Complete Guide In Software Development Engineer (SDE) interviews—especially for mid-level and senior roles—low-level design (LLD) rounds assess your ability to write clean, reusable, maintainable, and extensible code. The foundation of resolving these architectural challenges lies in the standard Gang of Four (GoF) Design Patterns. Rather than memorizing theoretical definitions, interviewers expect you to apply these patterns to real-world scenarios, identifying the trade-offs of each. Below is a comprehensive guide to the 12 most frequently asked design patterns in LLD interviews, categorized by their classification (Creational, Structural, and Behavioral). Each pattern contains a concrete, real-world Java implementation and a detailed breakdown of design decisions. Creational Design Patterns Creational design patterns deal with object creation mechanisms. They abstract the instantiation process, making a system independent of how...