Learning pathsPricingChecking sign-in…
← LLD problem library

LLD-009 · DESIGN PRACTICE

Meeting Room Scheduler

Make interval semantics, atomic conflict checks and local-time recurrence explicit.

Local Java 21 reference verified: 222 checks. Learner drafts are not executed. Author self-review; independent review pending.

Existing introductory lesson · Version 1

Understand the problem

Design room discovery, booking, cancellation, rescheduling and bounded weekly recurrence. Prevent concurrent double booking and preserve the original booking when rescheduling fails. Explain why a weekly local meeting is not always 168 hours apart in UTC.

Before you start

  • Half-open intervals
  • Ordered maps and predecessor/successor queries
  • Atomic check-and-write
  • Instant vs local time and timezone rules

Scope and explicit decisions

  • One in-memory calendar over configured rooms. No participants, invitations, external calendar synchronization, authorization, persistence, waitlists or series-edit API is implemented.
  • Ranges are nonempty half-open [start,end) Instant values. Adjacent bookings are allowed. Need stores positive people count and required equipment; Room capacity/equipment must satisfy it.
  • Room availability from find is advisory. book rechecks eligibility and conflicts under the same monitor used by every mutation. No availability token reserves capacity.
  • book has no idempotency key. A retry after a successful lost response conflicts instead of returning the original result; clients must reconcile. Reschedule uses an expected version, while cancel of an already cancelled booking is a no-op.
  • Reschedule may change room and interval, retaining the original Need and booking ID. The target is validated before removing the source booking. Invalid room/equipment/conflict/version leaves the source unchanged.
  • weekly expands 1..52 local weekly starts in a named ZoneId. It rejects local times with zero or multiple valid offsets rather than silently shifting a gap or choosing one side of a fold.
  • Each occurrence has a fixed elapsed Duration after its resolved start. This is not a promise that the local end clock will always differ by that duration across a DST transition.
  • bookSeries validates all proposed ranges, their internal conflicts and existing room conflicts before publishing any. Expansion and booking are separate: an expanded occurrence is not reserved until the calendar accepts the series.

Worked examples

Input: Room A booked [0,10). Try [10,20) then [9,11).

Outcome: Back-to-back succeeds; overlapping request fails.

Endpoint semantics are part of the domain contract, not a UI formatting detail.

Input: Weekly 10:00 New York beginning March 1, 2026.

Outcome: March 1 and March 8 starts remain 10:00 local but differ by 167 elapsed hours.

Local recurrence and elapsed UTC intervals express different requirements.

Your design attempt

Each language has a separate draft across all three levels. Saving is device-only and does not sync to your account. Anyone using this browser profile can load it. Loading is always explicit.

Java 21 reference code is provided for local study. Use Run program in the editor to execute your code and test driver. Program runs show output; they do not grade your design.

Solution.java
Run program · Java 21

No unsaved changes · 208/20000

Self-review

Checks apply to this visit. Self-review is not a passed submission or verified mastery.

Try before revealing

Hint 1

Write one precise overlap predicate and test touching endpoints.

Hint 2

Which earlier or later booking can overlap a candidate in an otherwise disjoint timeline?

Hint 3

Validate every target before releasing an existing reservation.

Requirements and invariants

R1 — Active reservations in one room never overlap. book protects I1.

R2 — Failed rescheduling retains the old booking and its ownership. reschedule protects I2.

R3 — A recurring series either passes every conflict check or creates no bookings. bookSeries protects I1.

R4 — Ambiguous/nonexistent local recurrence starts must be rejected explicitly. weekly protects I3.

I1: Each room timeline contains disjoint active intervals whose map key equals their start Instant.

I2: Every active Booking has one timeline entry; a cancelled record has none, and successful updates increase version.

I3: Each expanded local start resolves to exactly one offset under the chosen zone rules.

Derive the design

A full scan defines correctness; ordered neighbors improve conflict lookup

A simple implementation compares every existing booking with the proposed interval using a.start<b.end && b.start<a.end. It is correct and useful as an independent oracle, but costs O(B) per room. Because accepted room timelines are already disjoint, only the predecessor and successor around the candidate start can overlap. The reference uses a TreeMap and skips the same booking during reschedule.

Alternative: Interval trees support more general overlapping interval collections, but here the no-overlap invariant makes ordered neighbors sufficient. A database implementation needs a transactional exclusion/serialization contract; a UI precheck does not protect against concurrent writers.

Clarify time before choosing an index

Ask about endpoint inclusion, room equipment, recurrence zones and the meaning of duration. An efficient interval tree cannot rescue an ambiguous contract.

Exploit only proven index invariants

Room timelines are disjoint because every mutation preserves admission checks. That is what makes predecessor/successor sufficient. If manual imports can insert overlaps, this proof no longer holds.

Plan atomic updates across source and target

Reschedule validates against a view that ignores its own old interval but still sees every other booking. Remove/insert happens only after all checks pass.

Make recurrence a bounded transaction

Expand candidate instants and validate all of them before committing a series. A failure in the final occurrence should not silently leave the first occurrences booked unless partial success was explicitly chosen.

Responsibilities and ownership

Range

Owns: Validated start/end instants

Methods: overlaps contract

Defines half-open semantics once.

Room / Need

Owns: Immutable capacity/equipment and requested requirements

Methods: Validated records

Eligibility is separate from temporal conflict.

Calendar

Owns: Room timelines, booking identities and atomic mutations

Methods: find, book, bookSeries, reschedule, cancel

One monitor protects validation and index updates.

weekly

Owns: Bounded local-time expansion policy

Methods: weekly(first,zone,duration,count)

No room ownership or persistence; returns immutable candidate intervals.

Atomic booking and reschedule

find is advisory. Every mutation rechecks under the calendar monitor.

  1. Requested room / time / Need
  2. ↓ Expected version for updates

    Validate eligibility + neighbors
  3. ↓ No source removal before validation

    Commit timeline and ID record
  4. ↓ Version identifies accepted state

    Return immutable Booking
Weekly occurrence expansion

A series of resolved instants is not itself a reservation until admission succeeds.

  1. Local start + ZoneId
  2. ↓ Reject gaps and folds by policy

    Exactly one valid offset?
  3. ↓ Preserve local start across offset changes

    Resolve Instant + elapsed Duration
  4. ↓ Only then reserve room timelines

    Validate whole series

Data model, schema and relationships

Rooms own scheduling resources, bookings own half-open intervals, and an optional recurrence series groups concrete occurrences. A series rule does not replace actual conflict-checked intervals.

Implementation boundary: The local ordered-neighbor scheduler supports bounded atomic weekly series and explicit timezone ambiguity rejection. Durable user/calendar metadata is an extension.

These are proposed logical schemas with field contracts, not executable database migrations. PK identifies a row, FK references an existing parent, UK is a uniqueness rule. A composite PK identifies the combination. Nullable references are marked with ?. A foreign key protects identity; cross-row limits, timing and state transitions also require the transaction rules below.

room: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

room relationshipsroom 1 to room_booking 0..*: hosts intervals. room_booking.room_id → room.id.hosts intervals1 parent · 0..* childrenroom_bookingid [PK]room_id [FK]organizer_id [FK]roomid [PK]capacity
organizer: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

organizer relationshipsorganizer 1 to series 0..*: owns series. series.organizer_id → organizer.id. organizer 1 to room_booking 0..*: creates bookings. room_booking.organizer_id → organizer.id.owns series1 parent · 0..* childrenseriesid [PK]organizer_id [FK]timezonecreates bookings1 parent · 0..* childrenroom_bookingid [PK]room_id [FK]organizer_id [FK]organizerid [PK]name
series: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

series relationshipsseries 0..1 to room_booking 0..*: groups occurrences. room_booking.series_id → series.id.groups occurrences0..1 parent · 0..* childrenroom_bookingid [PK]room_id [FK]organizer_id [FK]seriesid [PK]organizer_id [FK]timezone

Entity definitions and field contracts

room

One independently exclusive resource.

FieldType / keyMeaning and reference
iduuid · PKRoom identity
capacityinteger · VALUEParticipant capacity

Code mapping: Room identity.

  • Capacity positive; features are separate policy inputs.

organizer

Booking owner identity.

FieldType / keyMeaning and reference
iduuid · PKOrganizer identity
nametext · VALUEDisplay name

Code mapping: External account metadata.

  • Authorization is separate from collision detection.

series

Recurrence intent and interpretation.

FieldType / keyMeaning and reference
iduuid · PKSeries identity
organizer_iduuid · FKOwner · organizer.id
timezonetext · VALUEIANA zone
ruletext · VALUEBounded weekly recurrence

Code mapping: Series command/result.

  • Reference bounds number of occurrences; reject gap/ambiguous local timestamps.

room_booking

Concrete accepted interval.

FieldType / keyMeaning and reference
iduuid · PKBooking identity
room_iduuid · FKExclusive resource · room.id
organizer_iduuid · FKOwner · organizer.id
series_iduuid? · FKOptional recurrence group · series.id
start_attimestamp · VALUEInclusive start instant
end_attimestamp · VALUEExclusive end instant

Code mapping: Booking records and ordered start map.

  • start_at<end_at; no overlapping active intervals per room; touching endpoints allowed.

Relationship cardinalities and their reasons

Parent → childMultiplicityFK / why
organizer → series

owns series

1 parent per child; 0..* children per parentseries.organizer_id → organizer.id

One owner may define many bounded recurrence requests.

room → room_booking

hosts intervals

1 parent per child; 0..* children per parentroom_booking.room_id → room.id

A room has historical bookings with non-overlap enforcement.

organizer → room_booking

creates bookings

1 parent per child; 0..* children per parentroom_booking.organizer_id → organizer.id

Ownership is independent of room identity.

series → room_booking

groups occurrences

0..1 parent per child; 0..* children per parentroom_booking.series_id → series.id

One-off bookings have no series; a series has concrete accepted occurrences.

Transaction boundaries

  1. Book/reschedule: exclude only the booking being moved, validate predecessor/successor overlap and commit removal/insertion together.
  2. Series: expand bounded instants, check internal/existing conflicts, then commit every occurrence or none.

Indexes and uniqueness

  1. Ordered index(room_id,start_at) supports neighbor checks for disjoint intervals.
  2. Use a database range exclusion constraint or room-level lock; a start-time unique index alone does not prevent overlap.

Schema traps and failure cases

  1. Concurrent different-start inserts can overlap despite unique start timestamps.
  2. DST interpretation cannot silently shift a requested local time without contract agreement.

Trace the records through one operation

  1. Book [10:00,11:00); [11:00,12:00) remains legal.
  2. Reschedule validates target neighbors before removing original interval.
  3. If occurrence 3 of a weekly series conflicts, none of the new series is committed.

Method contracts

find(Need,Range) → room IDs

Filter current eligibility and conflicts. Results have deterministic room-ID ordering but are advisory snapshots.

book(roomId,Need,Range) → Booking

Revalidate room eligibility and nonoverlap, then create version-zero booking and index entry atomically.

reschedule(id,targetRoom,Range,expectedVersion)

Reject stale/cancelled identity or unavailable target. Compute the replacement before removing the old index; preserve ID/Need and increment version.

cancel(id,expectedVersion)

Active cancellation requires current version; remove its index and retain a terminal historical record. Repeat terminal cancellation returns that record.

weekly(...); bookSeries(roomId,Need,ranges)

Expansion rejects gaps/folds. Series admission sorts <=52 proposed ranges, checks each against existing timeline and adjacent proposed range, then commits all.

What, when, why and how

Each concept is attached to the requirement it protects, its implementation, an alternative and a test.

Half-open time ranges

what
Include the start and exclude the end: [start,end).
when
One meeting ends exactly when the next begins.
why
Endpoint ambiguity can reject valid back-to-back bookings or accept actual overlaps.
how
Range validates start<end. overlaps uses strict comparisons in both directions.

Code: Range, overlaps · Invariants: I1

Alternatives, cost and failure example

Alternative: Closed intervals require a different overlap definition and a decision about shared endpoint instants.

Cost: One clear predicate makes tests and indexes simpler; conversion to display time is separate.

Counterexample: Treat [0,10) and [10,20) as overlapping using <= comparisons.

Test: Accept adjacent intervals and reject [9,11) against either neighbor.

Ordered-neighbor conflict detection

what
Use the established nonoverlap invariant to inspect only nearby reservations.
when
A room has many bookings and admission is frequent.
why
A full scan repeats unnecessary comparisons, while checking only the next start misses a containing predecessor.
how
Find floorEntry(start) and ceilingEntry(start); during reschedule skip the original booking in both directions. Test either retained neighbor with overlaps.

Code: conflict, reschedule · Invariants: I1

Alternatives, cost and failure example

Alternative: A list scan is simpler for tiny calendars and remains the test oracle. An interval tree is warranted only for richer overlap queries.

Cost: O(log B) neighbor lookup per room; series sorts O(S log S) and checks S neighbor pairs under the lock.

Counterexample: New [5,6) lies inside old [0,10); checking only a later start misses it.

Test: Compare indexed admission with a seeded independent full-scan model across varied intervals.

Validate target before releasing source

what
Reschedule is one atomic transfer of ownership.
when
A room/interval change can fail or race another booking.
why
Deleting the old booking first loses a valid reservation when the target is unavailable.
how
Validate expectedVersion, eligibility and target conflict with the current record ignored. Only then replace old/new timeline entries and booking snapshot under one monitor.

Code: reschedule, bookSeries · Invariants: I2

Alternatives, cost and failure example

Alternative: A durable database can use one transaction, but separate API calls to cancel then create do not provide the same guarantee.

Cost: A single calendar lock is easy to reason about but limits parallel room throughput. Per-room locks need ordering for cross-room changes.

Counterexample: Cancel source, then discover target lacks a screen or overlaps another event.

Test: Assert failed target validation leaves the original interval; race two same-room bookings and get one winner.

Recurrence as local rule plus explicit zone policy

what
Resolve weekly local starts in a named timezone, then derive instants.
when
Meetings span clock-offset changes.
why
Adding 168 hours in UTC can shift the local clock time, and default normalization can silently move nonexistent times.
how
Use LocalDateTime.plusWeeks, inspect getValidOffsets and require exactly one. Derive end by fixed elapsed Duration. Keep expansion bounded and separate from room admission.

Code: weekly · Invariants: I3

Alternatives, cost and failure example

Alternative: A product may choose earlier/later offset for folds or skip gap occurrences, but that must be visible policy and independently tested.

Cost: Zone-rule versions can change; persisting only future instants loses recurrence intent, while persisting only rules can change expansion after updates.

Counterexample: March 1 to March 8 at 10:00 New York spans 167 UTC hours in the tested zone rules.

Test: Reject March 8 02:30 gap and November 1 01:30 fold; preserve 10:00 across spring transition.

Read and run the Java reference

Reveal complete Solution.java

Java 21. Save as Solution.java; compile with javac --release 21. The repository also contains Test.java and the lld:java verification command. This reference implements the local contract above.

Solution.java Java 21
import java.time.*;
import java.util.*;
/** Atomic in-memory calendar. Intervals are half-open instants; recurrence starts in a named zone. */
public final class Solution {
    private Solution() {}
    public record Range(Instant start,Instant end){public Range{Objects.requireNonNull(start);Objects.requireNonNull(end);if(!start.isBefore(end))throw new IllegalArgumentException("range");}}
    public record Need(int people,Set<String> equipment){public Need{if(people<1)throw new IllegalArgumentException("people");equipment=Set.copyOf(equipment);}}
    public record Room(String id,int capacity,Set<String> equipment){public Room{if(id==null||id.isBlank()||capacity<1)throw new IllegalArgumentException("room");equipment=Set.copyOf(equipment);}}
    public record Booking(String id,String roomId,Range range,Need need,int version,boolean cancelled){}
    public static boolean overlaps(Range a,Range b){return a.start().isBefore(b.end())&&b.start().isBefore(a.end());}
    public static List<Range> weekly(LocalDateTime first,ZoneId zone,Duration duration,int occurrences){
        Objects.requireNonNull(first);Objects.requireNonNull(zone);Objects.requireNonNull(duration);
        if(occurrences<1||occurrences>52||duration.isZero()||duration.isNegative())throw new IllegalArgumentException("recurrence");
        var ranges=new ArrayList<Range>();
        for(int i=0;i<occurrences;i++){
            var local=first.plusWeeks(i);var offsets=zone.getRules().getValidOffsets(local);
            if(offsets.size()!=1)throw new IllegalArgumentException("ambiguous or nonexistent local time");
            Instant start=local.toInstant(offsets.getFirst());ranges.add(new Range(start,start.plus(duration)));
        }
        return List.copyOf(ranges);
    }
    public static final class Calendar {
        private final Map<String,Room> rooms=new TreeMap<>();
        private final Map<String,TreeMap<Instant,Booking>> timelines=new HashMap<>();
        private final Map<String,Booking> bookings=new HashMap<>();
        private long sequence;
        public Calendar(List<Room> rooms){for(var r:rooms){if(this.rooms.put(r.id(),r)!=null)throw new IllegalArgumentException("duplicate room");timelines.put(r.id(),new TreeMap<>());}}
        private Room room(String id){var r=rooms.get(id);if(r==null)throw new IllegalArgumentException("room");return r;}
        private boolean fits(Room r,Need n){return r.capacity()>=n.people()&&r.equipment().containsAll(n.equipment());}
        private boolean conflict(String roomId,Range range,String ignore){
            var map=timelines.get(roomId);var before=map.floorEntry(range.start());
            if(before!=null&&before.getValue().id().equals(ignore))before=map.lowerEntry(before.getKey());
            if(before!=null&&overlaps(before.getValue().range(),range))return true;
            var after=map.ceilingEntry(range.start());
            if(after!=null&&after.getValue().id().equals(ignore))after=map.higherEntry(after.getKey());
            return after!=null&&overlaps(after.getValue().range(),range);
        }
        public synchronized List<String> find(Need need,Range range){Objects.requireNonNull(need);Objects.requireNonNull(range);return rooms.values().stream().filter(r->fits(r,need)&&!conflict(r.id(),range,"")).map(Room::id).toList();}
        public synchronized Booking book(String roomId,Need need,Range range){
            Objects.requireNonNull(need);Objects.requireNonNull(range);if(!fits(room(roomId),need)||conflict(roomId,range,""))throw new IllegalStateException("unavailable");
            var b=new Booking("B"+(++sequence),roomId,range,need,0,false);timelines.get(roomId).put(range.start(),b);bookings.put(b.id(),b);return b;
        }
        public synchronized List<Booking> bookSeries(String roomId,Need need,List<Range> occurrences){
            Objects.requireNonNull(need);var ranges=new ArrayList<>(List.copyOf(occurrences));if(ranges.isEmpty()||ranges.size()>52)throw new IllegalArgumentException("series");
            ranges.sort(Comparator.comparing(Range::start));if(!fits(room(roomId),need))throw new IllegalStateException("requirements");
            for(int i=0;i<ranges.size();i++)if(conflict(roomId,ranges.get(i),"")||(i>0&&overlaps(ranges.get(i-1),ranges.get(i))))throw new IllegalStateException("series conflict");
            var result=new ArrayList<Booking>();for(var range:ranges)result.add(book(roomId,need,range));return List.copyOf(result);
        }
        public synchronized Booking reschedule(String id,String targetRoom,Range range,int expectedVersion){
            Objects.requireNonNull(range);var old=get(id);if(old.cancelled()||old.version()!=expectedVersion)throw new IllegalStateException("stale or cancelled");
            if(!fits(room(targetRoom),old.need())||conflict(targetRoom,range,id))throw new IllegalStateException("unavailable");
            var updated=new Booking(id,targetRoom,range,old.need(),Math.incrementExact(old.version()),false);
            timelines.get(old.roomId()).remove(old.range().start());timelines.get(targetRoom).put(range.start(),updated);bookings.put(id,updated);return updated;
        }
        public synchronized Booking cancel(String id,int expectedVersion){
            var old=get(id);if(old.cancelled())return old;
            if(old.version()!=expectedVersion)throw new IllegalStateException("stale version");
            var updated=new Booking(id,old.roomId(),old.range(),old.need(),Math.incrementExact(old.version()),true);timelines.get(old.roomId()).remove(old.range().start());bookings.put(id,updated);return updated;
        }
        public synchronized Booking get(String id){var b=bookings.get(id);if(b==null)throw new IllegalArgumentException("booking");return b;}
        public synchronized int activeCount(){return timelines.values().stream().mapToInt(Map::size).sum();}
    }
}

conflict checks both temporal neighbors

An earlier containing booking and a later-starting overlap are different cases. Skipping the original once per direction supports self-overlapping reschedules without losing the adjacent real conflict.

reschedule builds before mutating

The new Booking and incremented version are ready before removing the original timeline entry. Routine validation failures leave the old booking usable.

weekly rejects automatic clock normalization

getValidOffsets exposes gaps/folds instead of relying on convenience methods that choose or shift a local time implicitly.

bookSeries validates its own ranges too

Existing calendars can be conflict-free while the requested series overlaps itself. Sort and compare neighboring proposed ranges before any insertion.

Complexity and limits

  • find checks R rooms with O(log B) temporal lookup each, plus equipment subset checks; results are in configured sorted-ID order.
  • book/cancel/reschedule update ordered maps in O(log B), with expected O(1) booking-ID lookup. All run under one monitor.
  • bookSeries sorts S<=52 ranges and validates O(S log B+S log S) before committing O(S log B). Expansion costs O(S) zone resolutions for the bounded rule.
  • Space grows O(B+history+R); cancelled records remain for identity/reconciliation. This reference has no retention cleanup or persistence.

Walk through the actual reference

These walkthrough fixtures are emitted only after the Java harness verifies their operations and expected state. Concurrency tests additionally exercise competing callers.

Half-open intervals and safe reschedule

Step 1 of 2: Book [0,10), then [10,20)

Both accepted in room A

Invariant: Touching endpoints do not overlap

Read the complete trace table
ActionState after actionInvariant
Book [0,10), then [10,20)Both accepted in room ATouching endpoints do not overlap
Reschedule first to [15,25)Rejected; original interval retainedValidation precedes namespace mutation

Weekly local time crosses DST

Step 1 of 2: Expand March 1 and March 8 at 10:00 New York

Starts stay 10:00 local, UTC gap 167 hours

Invariant: Weekly local time is not a fixed 168-hour UTC interval

Read the complete trace table
ActionState after actionInvariant
Expand March 1 and March 8 at 10:00 New YorkStarts stay 10:00 local, UTC gap 167 hoursWeekly local time is not a fixed 168-hour UTC interval
Attempt March 8 at 02:30Rejected: nonexistent local timeNo silent timezone normalization

Mistakes and counterexamples

Use closed endpoints accidentally

Failing case: A meeting at 10 starts exactly when the previous ends.

Repair: Choose and test half-open intervals explicitly.

Only perform an availability read

Failing case: Two callers see a free room and then both write.

Repair: Put final conflict check and insertion under one authoritative atomic boundary.

Cancel then create to reschedule

Failing case: The target conflicts after source deletion.

Repair: Validate target first and transfer ownership atomically.

Repeat by adding fixed UTC weeks

Failing case: The local start time changes after DST.

Repair: Store local rule/zone and resolve each occurrence with declared gap/fold handling.

Engineer → Senior → Staff

Choose a deep dive

Implement precise booking semantics.

Executed: eligibility, half-open overlap, discovery, booking and cancellation.

Change exercise

Add containment, adjacency and invalid-empty-range tests.

Worked answer guidance

Use strict overlap comparisons. A containing predecessor must be checked even if no future booking starts inside the proposed interval.

Interview follow-ups

Why store Instants in the timeline?

They identify actual moments for conflict comparison. User-local formatting and recurrence rules are resolved at a separate boundary.

Does find reserve a room?

No. It is advisory; final admission must recheck under the authoritative owner.

Return to the lesson →

Provenance and scope of evidence

Original teaching material. The local contract is implemented by Solution.java and tested by Test.java on Java 21. Distributed persistence, real provider/device adapters and deployment are not claimed.

Reference verification is not independent editorial review, production deployment verification or a claim that distributed adapters are implemented.