name: Overlapping Intervals
slug: intervals
difficulty_level: 2

description: >
  Process and manipulate intervals (ranges) that may share common regions.
  The key insight is that sorting intervals by start time allows efficient
  detection and handling of overlaps through a single linear pass.

when_to_use: |
  - Merging overlapping intervals
  - Inserting an interval into a sorted list
  - Finding gaps between intervals
  - Meeting room scheduling
  - Finding interval intersections

metaphor: |
  Imagine scheduling meeting rooms. Each meeting is an interval of time. When
  two meetings overlap, you need either two rooms or to merge them into one
  longer booking. By sorting meetings by start time, you can easily spot
  overlaps—if the next meeting starts before the current one ends, they overlap.

  Another analogy: merging overlapping highlighter marks on a page. Sort them
  left to right, and if one mark starts before the previous ends, combine them
  into one continuous highlight.

core_concept: |
  The **interval pattern** relies on sorting intervals by start time. Once sorted,
  overlapping detection becomes simple:

  **Two intervals [a, b] and [c, d] overlap if c <= b** (assuming a <= c after sorting)

  Key operations:
  1. **Merge**: Extend the current interval's end to include overlapping intervals
  2. **Insert**: Find where the new interval overlaps and merge as needed
  3. **Count overlaps**: Track how many intervals are "active" at any point

  For problems needing simultaneous tracking (like minimum meeting rooms), use
  a **sweep line** approach: sort all start and end points together, then sweep
  through counting active intervals.

visualization: |
  **Merging overlapping intervals:**

  ```
  Input: [[1,3], [2,6], [8,10], [15,18]]
         (sorted by start)

  Process [1,3]: result = [[1,3]]

  Process [2,6]: 2 <= 3? Yes, overlap!
                 Merge: [1, max(3,6)] = [1,6]
                 result = [[1,6]]

  Process [8,10]: 8 <= 6? No, no overlap
                  result = [[1,6], [8,10]]

  Process [15,18]: 15 <= 10? No, no overlap
                   result = [[1,6], [8,10], [15,18]]
  ```

  **Visualized on number line:**

  ```
  Before:  [1---3]
              [2------6]
                          [8--10]
                                     [15--18]

  After:   [1--------6]  [8--10]     [15--18]
  ```

  **Meeting rooms (sweep line):**

  ```
  Meetings: [[0,30], [5,10], [15,20]]

  Events (sorted):
  time=0:  +1 (start)  active=1
  time=5:  +1 (start)  active=2 ← max
  time=10: -1 (end)    active=1
  time=15: +1 (start)  active=2 ← max
  time=20: -1 (end)    active=1
  time=30: -1 (end)    active=0

  Max concurrent = 2 → need 2 meeting rooms
  ```

code_template: |
  def merge_intervals(intervals: list[list[int]]) -> list[list[int]]:
      """Merge overlapping intervals."""
      if not intervals:
          return []

      # Sort by start time
      intervals.sort(key=lambda x: x[0])

      result = [intervals[0]]

      for start, end in intervals[1:]:
          last_end = result[-1][1]

          if start <= last_end:  # Overlap
              result[-1][1] = max(last_end, end)  # Extend
          else:
              result.append([start, end])

      return result


  def insert_interval(intervals: list[list[int]],
                      new: list[int]) -> list[list[int]]:
      """Insert and merge a new interval into sorted list."""
      result = []
      i = 0
      n = len(intervals)

      # Add all intervals before new interval
      while i < n and intervals[i][1] < new[0]:
          result.append(intervals[i])
          i += 1

      # Merge overlapping intervals with new
      while i < n and intervals[i][0] <= new[1]:
          new[0] = min(new[0], intervals[i][0])
          new[1] = max(new[1], intervals[i][1])
          i += 1

      result.append(new)

      # Add remaining intervals
      while i < n:
          result.append(intervals[i])
          i += 1

      return result


  def min_meeting_rooms(intervals: list[list[int]]) -> int:
      """Find minimum meeting rooms needed (sweep line)."""
      events = []

      for start, end in intervals:
          events.append((start, 1))   # +1 for start
          events.append((end, -1))    # -1 for end

      # Sort by time, with ends before starts at same time
      events.sort(key=lambda x: (x[0], x[1]))

      max_rooms = 0
      current_rooms = 0

      for _, delta in events:
          current_rooms += delta
          max_rooms = max(max_rooms, current_rooms)

      return max_rooms


  def interval_intersection(A: list[list[int]],
                            B: list[list[int]]) -> list[list[int]]:
      """Find intersection of two sorted interval lists."""
      result = []
      i = j = 0

      while i < len(A) and j < len(B):
          # Find overlap
          start = max(A[i][0], B[j][0])
          end = min(A[i][1], B[j][1])

          if start <= end:
              result.append([start, end])

          # Advance the interval that ends first
          if A[i][1] < B[j][1]:
              i += 1
          else:
              j += 1

      return result


  def can_attend_all(intervals: list[list[int]]) -> bool:
      """Check if a person can attend all meetings."""
      intervals.sort(key=lambda x: x[0])

      for i in range(1, len(intervals)):
          if intervals[i][0] < intervals[i-1][1]:
              return False  # Overlap found

      return True

recognition_signals:
  - "intervals"
  - "merge"
  - "overlapping"
  - "meeting rooms"
  - "schedule"
  - "time slots"
  - "range"
  - "insert interval"
  - "non-overlapping"
  - "intersection"

common_mistakes:
  - title: Not sorting first
    description: |
      Trying to process unsorted intervals leads to incorrect results because
      overlaps aren't detected properly.
    fix: |
      Always sort intervals by start time before processing:
      ```python
      intervals.sort(key=lambda x: x[0])
      ```

  - title: Wrong overlap condition
    description: |
      Using `start < last_end` instead of `start <= last_end` misses adjacent
      intervals that should be merged (like [1,2] and [2,3]).
    fix: |
      Use `<=` for touching intervals, `<` for strict overlap only. Check problem
      requirements for whether touching counts as overlapping.

  - title: Not updating end correctly when merging
    description: |
      Setting `end = new_end` instead of `end = max(old_end, new_end)` fails when
      a smaller interval is contained within a larger one.
    fix: |
      Always take the maximum:
      ```python
      result[-1][1] = max(result[-1][1], end)
      ```

  - title: Off-by-one with closed vs open intervals
    description: |
      Confusion about whether interval endpoints are inclusive `[a, b]` or
      exclusive `[a, b)` causes incorrect overlap detection.
    fix: |
      Clarify the convention from the problem. Most problems use closed intervals
      where both endpoints are included.

variations:
  - name: Merge intervals
    description: |
      Combine all overlapping intervals into non-overlapping intervals.
    example: "Merge Intervals"

  - name: Insert interval
    description: |
      Insert a new interval into a sorted list, merging with any overlapping
      intervals.
    example: "Insert Interval"

  - name: Meeting rooms
    description: |
      Find minimum resources needed for concurrent intervals using sweep line
      or min-heap.
    example: "Meeting Rooms II, Car Pooling"

  - name: Interval intersection
    description: |
      Find common regions between two lists of intervals using two pointers.
    example: "Interval List Intersections"

  - name: Non-overlapping intervals
    description: |
      Find minimum removals to make all intervals non-overlapping. Greedy
      approach: keep intervals that end earliest.
    example: "Non-overlapping Intervals, Erase Overlap Intervals"

related_patterns:
  - greedy
  - two-pointers

prerequisite_patterns: []
