SwadhyAI Notes

All Low-Level Design notes

Machine-coding case study

Meeting / calendar scheduler

Tuesday morning. You need to schedule A, B, and a room every Tuesday. A is already busy 10:00–11:00. A 200-line canSchedule on CalendarManager can hard-code this week's pairwise checks, then break when the weekly series arrives.

That is the whole problem this page solves. A calendar scheduler finds conflict-free slots for people (and rooms), including recurring series. Overlap math lives on ; calendars answer busy or conflict; a expands inside a window. Put those three jobs on one manager and recurrence will tear the design apart.

Four types, four jobs

1

Owns overlaps and duration so clash math has one home.

2

Expands occurrences in a window so you never insert infinite rows.

3

Calendar

Answers hasConflict or busy ranges for one person or one room.

4

Scheduler

Proposes a gap. book re-checks conflict under a lock.

What actually happens when a meeting is booked

Participant, Meeting, and Calendar are entities. is a value: a meeting occupies one range on each participant's calendar. Conflict means two ranges overlap for the same participant or room. That is all a clash is. The rest of the design is about who is allowed to answer that question.

A is not a pile of stored meetings. It is a rule that expands to occurrences in a query window (weekly, with an until date, maybe exceptions). Scheduling is a query: given participants, a duration, and a window, find a TimeRange where none have a conflict. A service can ask each calendar for busy ranges and look for a gap. Do not put room booking, email invites, and timezone conversion on the same class as overlap math. See domain modeling and Specification for working hours as a filter on candidate slots.

Timezones and : store for overlap math, but model recurrence as LocalTime plus ZoneId (wall clock), not DayOfWeek plus Instant alone. Daylight saving (DST) spring gaps and fall overlaps need an explicit policy when expanding occurrences. Tests use a fixed clock and explicit ranges; LocalDateTime.now() in overlap checks will flake. See deterministic design.

Simple rule of thumb

Once you can name the four types, three habits keep CalendarManager from mixing overlap, rooms, and invites on one type (the Tuesday failure in the hero).

1. Give clash math one home. is the for overlaps. Construct it valid or throw. Do not rediscover inverted ranges (11:00 then 10:00) inside canSchedule.

2. Expand, do not insert infinity. A plus an exception list is the series. Expand inside the query window.

3. Say the interval convention out loud. Pick or closed-closed before you code, write the test, and do not mix. filter candidates; rooms are calendars, not a special God manager.

Rule of thumb

Overlap has one home. Recurrence is expansion, not storage of infinity. findSlot is a query; book re-checks. Invites and RSVP can wait.

From problem to mechanism

Here is the Tuesday booking again, slowed down so you can see where the naive manager fails and what each type is for.

Problem. Find a conflict-free slot for people and a room, including a recurring series. A meeting occupies a on each participant's calendar.

Naive system. One CalendarManager with canSchedule that reimplements overlap, rooms, and invites. Recurrence is "insert every weekly row forever." Tests call LocalDateTime.now().

Break it. The weekly series arrives, or two schedulers grab the last room (). Overlap copied in three places disagrees. DST (daylight saving) spring gaps skip an occurrence you thought you stored. Adjacent meetings conflict or do not, depending on which method you called.

Mechanism. is the for clash math. Calendar answers hasConflict or busy ranges for one person or room. A expands in a window to Instant ranges. Scheduler intersects busy ranges and proposes a slot; it does not do overlap arithmetic itself. Book is a command that re-checks under a lock on the room calendar.

The diagram below is the same overlap rule the expert owns. A overlaps B; B overlaps C; A and C do not overlap if the end is exclusive.

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
flowchart LR
    A["A 10:00–11:00"]
    B["B 10:30–11:30"]
    C["C 11:00–12:00"]
    A --- B
    B --- C
    A -.->|"no overlap if end exclusive"| C

Who owns which job

The mechanism names four types. This table is the responsibility split you defend in an interview: each row is one reason CalendarManager is not the .

TypeDoesDoes not
TimeRangeoverlaps, durationEmail invites
RecurrenceRuleExpand in a windowInsert infinite rows
CalendarhasConflict / busy rangesFind slots for a group
SchedulerIntersect busy, propose slotOverlap arithmetic itself

Java API sketch

The sketch matches the table: throws on inverted construction, expands LocalTime plus ZoneId to ranges, and Scheduler only asks calendars for busy windows.

java
final class TimeRange {
  private final Instant start;
  private final Instant end;

  TimeRange(Instant start, Instant end) {
    if (!start.isBefore(end)) throw new InvertedRange();
    this.start = start;
    this.end = end;
  }

  boolean overlaps(TimeRange other) {
    return start.isBefore(other.end) && other.start.isBefore(end);
  }
}

interface RecurrenceRule {
  List<TimeRange> occurrences(TimeRange window);
}

/** Weekly at a wall-clock LocalTime in a ZoneId: not DayOfWeek + Instant alone. */
class WeeklyRule implements RecurrenceRule {
  private final DayOfWeek day;
  private final LocalTime localStart; // e.g. 10:00 in the calendar's zone
  private final Duration length;
  private final ZoneId zone;
  private final LocalDate until;
  public List<TimeRange> occurrences(TimeRange window) {
    // expand LocalDate + localStart → ZonedDateTime → Instant;
    // DST: spring gap may skip/adjust; fall overlap pick offset policy
  }
}

class Calendar {
  private final List<Meeting> meetings = new ArrayList<>();
  boolean hasConflict(TimeRange range) {
    return meetings.stream().anyMatch(m -> m.occupies().overlaps(range));
  }
}

class Scheduler {
  Optional<TimeRange> findSlot(List<Calendar> people, Duration duration, TimeRange window);
}

Worked example: two ranges and a weekly series

Use the Tuesday story with numbers so you can walk a slot search, then a six-week series, without a new type.

A is busy 10:00–11:00. B is free. Room R is busy 10:30–11:00. Duration 30 minutes, window 09:00–12:00. Scheduler asks each calendar for busy ranges, merges, and finds a gap. 10:00–10:30 fails on A. 11:00–11:30 is free for everyone if 11:00 is exclusive of A's end (say out loud). That comparison lives on .

Weekly Tuesday 10:00 for six weeks: store LocalTime 10:00, ZoneId, and until LocalDate on WeeklyRule. occurrences(thisMonth) expands to ranges (watch DST gaps and overlaps) and returns the six TimeRanges. hasConflict expands, then overlaps. You did not insert six rows. An exception on week three is a skip list on the , not a deleted row in an infinite table.

Book is a command: re-check hasConflict under a lock on the room calendar. findSlot is stale by the time you write. Same as seats and parking: reserve the room or serialize on its calendar.

Numbers to know

  • Window 09:00–12:00, duration 30 minutes: the search bounds for this walkthrough.
  • A busy 10:00–11:00 and R busy 10:30–11:00, so 11:00–11:30 is the first shared gap.
  • Six weekly Tuesdays, skip week three: a rule plus an exception list, not six stored rows.

Invalid cases, races, and what still breaks

The walkthrough assumes valid ranges and a single writer. These are the inputs and races that reject a book, plus the follow-ups interviewers add after the core types work.

Invalid cases

  • Inverted range 11:00–10:00: throw at construction so overlap never sees start after end.
  • Two meetings 10:00–11:00 and 10:30–11:30 on one person: hasConflict is true, refuse the book.
  • Storing every weekly instance forever: keep a rule plus exceptions, expand in a window.
  • CalendarManager mixing overlap, rooms, and invites: split them; clash math has one home (see above).
  • LocalDateTime.now() in tests: flaky. Inject or pass explicit Instants.

Concurrency

Two schedulers booking the last room is . Reserve the room (like a seat) or serialize on the room calendar. still answers overlaps; the lock covers the re-check and the write.

What the follow-up changes

  • Working hours or no Fridays: a filters candidate ranges.
  • Exceptions to a series: a skip list on the RecurrenceRule, not deleted infinite rows.
  • Rooms as participants: same Calendar.hasConflict, no special God manager.

Trade-offs

  • keeps 10:00–11:00 and 11:00–12:00 free of each other.
  • Closed-closed makes those adjacent meetings conflict; pick one convention and test it.
  • Invites stay adapters. Overlap stays on TimeRange so RSVP can wait.

Tests that prove the seams

After the types and the race, this test is the seam you keep: overlaps, then Calendar.hasConflict uses that result. Recurrence and book can wait until this is green.

java
@Test
void overlappingRangesConflict() {
  TimeRange a = new TimeRange(t("10:00"), t("11:00"));
  TimeRange b = new TimeRange(t("10:30"), t("11:30"));
  assertTrue(a.overlaps(b));
  Calendar cal = new Calendar();
  cal.add(new Meeting(a));
  assertTrue(cal.hasConflict(b));
}

Wrap-up

You have the failure, the four types, and the race. Use the forty-five minute split to practise, then follow the related notes for values, filters, and clocks.

How to spend forty-five minutes

  1. 0–8TimeRange constructor plus overlaps tests, and reject one inverted range.
  2. 8–20Calendar.hasConflict: two overlapping meetings on one person must refuse.
  3. 20–32WeeklyRule.occurrences in a window, without inserting infinite weekly rows.
  4. 32–40Scheduler.findSlot as a query over each calendar's busy ranges.
  5. 40–45book re-checks under a lock; working hours as Specification if asked.

Key takeaway

The for clash math is TimeRange, not a 200-line manager. Expand a recurring series in a window, then re-check overlaps on book. Invites and RSVP can wait.