Find earliest common meeting slot
Company: Uber
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Onsite
Quick Answer: An Uber Software Engineer onsite coding question: given N attendees' busy calendars as sorted half-open intervals plus per-attendee working hours, find the earliest time window of length d in which everyone is simultaneously free, or return -1. It covers the merge-busy/scan-gaps algorithm and complexity, then follow-ups on scaling to N participants, time zones and DST, very large fragmented inputs, clock-injection API design, and concurrency control for live calendar updates.
Constraints
- 1 <= N (number of attendees); each attendee has a working-hours window [ws, we) with ws < we
- Each attendee's busy list is sorted, non-overlapping, and uses half-open [start, end) intervals
- Total busy intervals M can be large (up to ~1e6 in the fragmented follow-up)
- Times are integers in minutes since the epoch and may be negative
- An empty calendar means that attendee is free for the entire window
- Return -1 if no qualifying slot exists (including when the common working window is shorter than d)
Examples
Input: ([[[60, 120], [180, 240]], [[0, 90], [150, 200]]], [[0, 480], [30, 480]], 30)
Expected Output: 120
Explanation: Common window [30,480). Merged busy = [0..120) (A's [60,120) clipped, B's [0,90)) then [150,240). Gap [120,150) has length 30 >= d, so earliest start is 120.
Input: ([[[0, 480]]], [[0, 480]], 30)
Expected Output: -1
Explanation: The single attendee is busy for the entire working window [0,480), leaving no free gap of length 30.
Input: ([[], []], [[0, 480], [60, 300]], 60)
Expected Output: 60
Explanation: Both calendars empty so everyone is free. Common window = [60,300); the earliest 60-minute slot starts at the window start, 60.
Input: ([[[100, 200]]], [[0, 480]], 50)
Expected Output: 0
Explanation: Free gap [0,100) before the first busy block has length 100 >= 50, so the earliest slot starts at 0.
Input: ([[[0, 100]]], [[0, 480]], 50)
Expected Output: 100
Explanation: Busy [0,100) covers the window start, so the first 50-minute free slot starts right after it at 100 (gap [100,480)).
Input: ([[[60, 120]], [[120, 180]]], [[0, 480], [0, 480]], 60)
Expected Output: 0
Explanation: Merged busy = [60,180) (the two blocks touch at 120 and merge). The leading gap [0,60) has length 60 >= d, so earliest start is 0.
Input: ([[[0, 60], [120, 180]]], [[0, 200]], 60)
Expected Output: 60
Explanation: Gaps are [60,120) length 60 (qualifies) and [180,200) length 20. First qualifying gap starts at 60.
Input: ([[[0, 60], [100, 180]]], [[0, 200]], 50)
Expected Output: -1
Explanation: Gaps are [60,100) length 40 and tail [180,200) length 20; neither reaches 50, so no slot exists.
Input: ([[[0, 100]], [[50, 200]]], [[0, 250]], 40)
Expected Output: 200
Explanation: Merged busy = [0,200) (overlapping blocks merge). Only free gap is the tail [200,250) length 50 >= 40, so earliest start is 200.
Input: ([[]], [[0, 100]], 100)
Expected Output: 0
Explanation: Empty calendar, common window exactly [0,100) length 100 == d, so the whole window is the slot starting at 0.
Input: ([[]], [[0, 100]], 101)
Expected Output: -1
Explanation: Common window length 100 is shorter than the required 101 minutes, so return -1.
Input: ([[[-50, -10]]], [[-100, 0]], 30)
Expected Output: -100
Explanation: Negative epoch-minute times. Leading gap [-100,-50) has length 50 >= 30, so the earliest slot starts at -100.
Hints
- Intersect all working-hour windows first: the feasible region is W = [max(ws), min(we)). If W is empty or shorter than d, return -1 immediately.
- Don't intersect free intervals pairwise across attendees — instead merge all attendees' busy intervals into one sorted timeline, then look at the gaps between consecutive busy blocks.
- Walk left to right tracking prev_end (start it at W.start). The first busy block whose start minus prev_end is >= d gives the answer prev_end. Remember to check the tail gap [last_end, W.end) too.
- Half-open matters: busy [a, b) followed by busy [b, c) are adjacent, not overlapping, and leave no free time at b — so treat touching blocks as merged and never report a zero-length slot.