DSA Fundamentals: Intervals - From Theory to LeetCode Practice

Jay Kye
16 min read

Master interval problems through theory and practical LeetCode problem solving. Covering interval merging, scheduling conflicts, insertion, and meeting room optimization strategies.

DSAIntervalsLeetCodeAlgorithmsProblem SolvingSchedulingGreedy

Interval problems are a fundamental category in algorithmic problem-solving, frequently appearing in coding interviews and real-world applications like scheduling systems, calendar management, and resource allocation. These problems require understanding how to efficiently handle overlapping ranges, merge intervals, and optimize resource usage.

This comprehensive guide combines theoretical understanding with practical problem-solving, featuring solutions to essential LeetCode problems that demonstrate core interval manipulation patterns.

Understanding Intervals

Interval Fundamentals

Intervals represent ranges of values, typically defined by a start and end point. In most problems, intervals are represented as pairs [start, end] where start ≤ end.

Key Interval Concepts

1. Interval Overlap:

  • Two intervals [a, b] and [c, d] overlap if: max(a, c) ≤ min(b, d)
  • Alternatively: a ≤ d and c ≤ b
  • Non-overlapping condition: b < c or d < a

2. Interval Merging:

  • When intervals overlap, merge them into: [min(a, c), max(b, d)]
  • Merged interval covers the entire range of both original intervals

3. Interval Ordering:

  • Sorting intervals by start time enables efficient processing
  • After sorting, we can process intervals in a single pass

Common Interval Problem Types

1. Merge Intervals:

  • Combine overlapping intervals into single intervals
  • Result: Non-overlapping intervals covering all original intervals

2. Scheduling Conflicts:

  • Determine if intervals can be scheduled without conflicts
  • Check if any two intervals overlap

3. Insert Interval:

  • Add new interval to sorted list of non-overlapping intervals
  • Merge with overlapping intervals if necessary

4. Resource Allocation:

  • Find minimum resources needed to handle all intervals
  • Example: Minimum meeting rooms required

Time Complexity Analysis

OperationTime ComplexitySpace ComplexityNotes
Sort intervalsO(n log n)O(1)Required for most interval problems
Merge intervalsO(n)O(n)After sorting, single pass
Check overlapsO(n)O(1)Linear scan after sorting
Insert intervalO(n)O(n)Linear scan and merge

Essential LeetCode Problems & Solutions

Let's explore fundamental interval problems that demonstrate core algorithmic patterns.

1. Merge Intervals (LeetCode 56)

Problem: Given an array of intervals where intervals[i] = [start_i, end_i], merge all overlapping intervals and return an array of non-overlapping intervals.

Approach: Sort intervals by start time, then iterate through and merge overlapping intervals.

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        # Sort intervals by start time
        intervals.sort(key=lambda x: x[0])
        
        # Initialize result with first interval
        merged = [intervals[0]]
        
        # Process remaining intervals
        for interval in intervals[1:]:
            # Check if current interval overlaps with last merged interval
            # Overlap condition: current start <= last end
            if merged[-1][1] >= interval[0]:
                # Merge: extend end time to maximum of both
                merged[-1][1] = max(interval[1], merged[-1][1])
            else:
                # No overlap: add as new interval
                merged.append(interval)
        
        return merged

Time Complexity: O(n log n) - Dominated by sorting
Space Complexity: O(n) - Result array storage

Key Concepts:

  • Sorting prerequisite: Sort by start time enables single-pass merge
  • Overlap detection: merged[-1][1] >= interval[0] checks if intervals overlap
  • Merge operation: max(interval[1], merged[-1][1]) extends end time
  • Greedy approach: Process intervals in order, merge when possible
  • Edge case: Empty intervals list handled by initial check

Example Walkthrough:

Input: [[1,3], [2,6], [8,10], [15,18]]
After sort: [[1,3], [2,6], [8,10], [15,18]] (already sorted)

Step 1: merged = [[1,3]]
Step 2: [2,6] overlaps with [1,3] → merge → [[1,6]]
Step 3: [8,10] doesn't overlap → add → [[1,6], [8,10]]
Step 4: [15,18] doesn't overlap → add → [[1,6], [8,10], [15,18]]

2. Meeting Rooms (LeetCode 252)

Problem: Given an array of meeting time intervals, determine if a person could attend all meetings.

Approach: Sort intervals by start time, check if any adjacent intervals overlap.

class Solution:
    def meetingRooms(self, intervals: List[List[int]]) -> bool:
        # Sort intervals by start time
        intervals.sort(key=lambda x: x[0])
        
        # Check adjacent intervals for overlap
        for i in range(1, len(intervals)):
            first_meeting = intervals[i-1]
            second_meeting = intervals[i]
            
            # Overlap condition: first meeting ends after second starts
            if first_meeting[1] > second_meeting[0]:
                return False
        
        return True

Time Complexity: O(n log n) - Dominated by sorting
Space Complexity: O(1) - Only using loop variables

Key Concepts:

  • Adjacent check: After sorting, only need to check consecutive intervals
  • Overlap condition: first_meeting[1] > second_meeting[0] detects conflict
  • Early termination: Return False immediately when conflict found
  • Single person constraint: Person can only attend one meeting at a time
  • Sorting optimization: Without sorting, would need O(n²) comparisons

Example Walkthrough:

Input: [[0,30], [5,10], [15,20]]
After sort: [[0,30], [5,10], [15,20]]

Check 1: [0,30] vs [5,10] → 30 > 5 → OVERLAP → return False

Input: [[7,10], [2,4]]
After sort: [[2,4], [7,10]]

Check 1: [2,4] vs [7,10] → 4 > 7? No → No overlap
All meetings can be attended → return True

3. Insert Interval (LeetCode 57)

Problem: Given a sorted array of non-overlapping intervals and a new interval, insert and merge if necessary.

Approach: Three-phase approach: add intervals before new interval, merge overlapping intervals, add remaining intervals.

class Solution:
    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
        merged = []
        n = len(intervals)
        i = 0
        
        # Phase 1: Add all intervals that end before new interval starts
        while i < n and intervals[i][1] < newInterval[0]:
            merged.append(intervals[i])
            i += 1
        
        # Phase 2: Merge all intervals that overlap with new interval
        while i < n and intervals[i][0] <= newInterval[1]:
            # Merge: update newInterval to cover both
            newInterval[0] = min(intervals[i][0], newInterval[0])
            newInterval[1] = max(intervals[i][1], newInterval[1])
            i += 1
        
        # Add the merged interval
        merged.append(newInterval)
        
        # Phase 3: Add all remaining intervals
        while i < n:
            merged.append(intervals[i])
            i += 1
        
        return merged

Time Complexity: O(n) - Single pass through intervals
Space Complexity: O(n) - Result array storage

Key Concepts:

  • Three-phase approach: Before, merge, after
  • Sorted input advantage: Can process in single pass without sorting
  • Overlap detection: intervals[i][0] <= newInterval[1] checks if intervals overlap
  • Merge expansion: Update newInterval boundaries to cover all overlapping intervals
  • Efficient insertion: O(n) time without needing to sort

Example Walkthrough:

Input: intervals = [[1,2], [3,5], [6,7], [8,10], [12,16]]
       newInterval = [4,8]

Phase 1: [1,2] ends before 4 → add → merged = [[1,2]]
Phase 2: 
  - [3,5] overlaps (3 <= 8) → merge → newInterval = [3,8]
  - [6,7] overlaps (6 <= 8) → merge → newInterval = [3,8]
  - [8,10] overlaps (8 <= 8) → merge → newInterval = [3,10]
  - [12,16] doesn't overlap (12 > 10) → stop
  Add merged: merged = [[1,2], [3,10]]
Phase 3: Add remaining → merged = [[1,2], [3,10], [12,16]]

4. Meeting Rooms II (LeetCode 253)

Problem: Given an array of meeting time intervals, find the minimum number of conference rooms required.

Approach: Use two pointers technique with separate start and end arrays. Track number of simultaneous meetings.

class Solution:
    def minMeetingRooms(self, intervals: List[List[int]]) -> int:
        # Sort intervals by start time
        intervals.sort(key=lambda x: x[0])
        
        # Extract start and end times
        start = []
        end = []
        for i in range(len(intervals)):
            start.append(intervals[i][0])
            end.append(intervals[i][1])
        
        # Sort end times separately
        end.sort()
        
        # Two pointers: one for start, one for end
        p1, p2 = 0, 0
        res, count = 0, 0
        
        # Process all start times
        while p1 < len(intervals):
            # If meeting starts before one ends, need new room
            if start[p1] < end[p2]:
                count += 1
                p1 += 1
            else:
                # Meeting ended, free up a room
                count -= 1
                p2 += 1
            
            # Track maximum rooms needed
            res = max(res, count)
        
        return res

Time Complexity: O(n log n) - Sorting dominates
Space Complexity: O(n) - Start and end arrays

Key Concepts:

  • Two-pointer technique: Track start and end events separately
  • Event-based approach: Treat start as +1 room, end as -1 room
  • Greedy counting: Count simultaneous meetings at each point
  • Maximum tracking: Answer is maximum count during processing
  • Why it works: Maximum overlap = minimum rooms needed

Example Walkthrough:

Input: [[0,30], [5,10], [15,20]]
After sort: [[0,30], [5,10], [15,20]]

start = [0, 5, 15]
end = [10, 20, 30] (sorted)

p1=0, p2=0: start[0]=0 < end[0]=10 → count=1, res=1
p1=1, p2=0: start[1]=5 < end[0]=10 → count=2, res=2
p1=2, p2=0: start[2]=15 > end[0]=10 → count=1, p2=1
p1=2, p2=1: start[2]=15 < end[1]=20 → count=2, res=2
p1=3 (done), p2=1: end[1]=20 → count=1
p1=3, p2=2: end[2]=30 → count=0

Maximum count = 2 → Need 2 rooms

Alternative Approach: Priority Queue (Min Heap)

import heapq

class Solution:
    def minMeetingRooms(self, intervals: List[List[int]]) -> int:
        # Sort by start time
        intervals.sort(key=lambda x: x[0])
        
        # Min heap to track end times of ongoing meetings
        heap = []
        
        for interval in intervals:
            # If earliest ending meeting has ended, reuse that room
            if heap and heap[0] <= interval[0]:
                heapq.heappop(heap)
            
            # Add current meeting's end time
            heapq.heappush(heap, interval[1])
        
        # Size of heap = number of rooms needed
        return len(heap)

Time Complexity: O(n log n) - Sorting + heap operations
Space Complexity: O(n) - Heap storage

Key Concepts:

  • Heap optimization: Track only end times of active meetings
  • Room reuse: If earliest meeting ends before new one starts, reuse room
  • Heap size: Represents number of simultaneous meetings
  • Cleaner logic: More intuitive than two-pointer approach

Core Algorithm Patterns from Today's Problems

1. Sort-First Pattern (Primary Pattern)

When to use:

  • Intervals are not sorted
  • Need to process intervals in chronological order
  • Overlap detection requires ordered processing

Examples: Merge Intervals, Meeting Rooms, Insert Interval

Key characteristics:

  • Sort by start time: intervals.sort(key=lambda x: x[0])
  • After sorting, can process in single pass
  • Time complexity: O(n log n) for sorting + O(n) for processing

Template:

def interval_problem(intervals):
    # Step 1: Sort by start time
    intervals.sort(key=lambda x: x[0])
    
    # Step 2: Process in order
    result = []
    for interval in intervals:
        # Process based on problem requirements
        if condition:
            # Merge, check overlap, etc.
            pass
        else:
            result.append(interval)
    
    return result

2. Three-Phase Insertion Pattern

When to use:

  • Inserting interval into sorted list
  • Need to merge with overlapping intervals
  • Input is already sorted (optimization opportunity)

Example: Insert Interval

Key characteristics:

  • Phase 1: Add intervals before new interval
  • Phase 2: Merge overlapping intervals
  • Phase 3: Add remaining intervals
  • O(n) time without sorting

Template:

def insert_interval(intervals, newInterval):
    merged = []
    i = 0
    
    # Phase 1: Before
    while i < len(intervals) and intervals[i][1] < newInterval[0]:
        merged.append(intervals[i])
        i += 1
    
    # Phase 2: Merge
    while i < len(intervals) and intervals[i][0] <= newInterval[1]:
        newInterval[0] = min(intervals[i][0], newInterval[0])
        newInterval[1] = max(intervals[i][1], newInterval[1])
        i += 1
    merged.append(newInterval)
    
    # Phase 3: After
    while i < len(intervals):
        merged.append(intervals[i])
        i += 1
    
    return merged

3. Event-Based Counting Pattern

When to use:

  • Finding maximum simultaneous events
  • Resource allocation problems
  • Counting overlaps at specific points

Example: Meeting Rooms II

Key characteristics:

  • Separate start and end events
  • Track count changes at each event
  • Maximum count = answer
  • Can use two pointers or priority queue

Template:

def min_resources(intervals):
    # Extract and sort events
    starts = sorted([i[0] for i in intervals])
    ends = sorted([i[1] for i in intervals])
    
    # Two pointers
    start_ptr, end_ptr = 0, 0
    count = 0
    max_count = 0
    
    while start_ptr < len(intervals):
        if starts[start_ptr] < ends[end_ptr]:
            count += 1
            start_ptr += 1
        else:
            count -= 1
            end_ptr += 1
        max_count = max(max_count, count)
    
    return max_count

4. Adjacent Comparison Pattern

When to use:

  • Checking if all intervals are non-overlapping
  • Simple conflict detection
  • Single pass after sorting

Example: Meeting Rooms

Key characteristics:

  • Sort first
  • Compare only adjacent intervals
  • Early termination on first conflict
  • O(n log n) time, O(1) space

Template:

def no_conflicts(intervals):
    intervals.sort(key=lambda x: x[0])
    
    for i in range(1, len(intervals)):
        if intervals[i-1][1] > intervals[i][0]:
            return False
    
    return True

Performance Optimization Tips

1. Leverage Sorted Input

# If input is already sorted, skip sorting step
def insert_interval_optimized(intervals, newInterval):
    # Input is sorted, so we can process directly
    # O(n) time instead of O(n log n)
    merged = []
    i = 0
    
    # ... rest of three-phase logic

Key insight: Always check if input is sorted before sorting.

2. Early Termination

# For conflict detection, return immediately
def can_attend_meetings(intervals):
    intervals.sort(key=lambda x: x[0])
    
    for i in range(1, len(intervals)):
        if intervals[i-1][1] > intervals[i][0]:
            return False  # Early exit on first conflict
    
    return True

Key insight: Stop processing as soon as answer is determined.

3. In-Place Merging

# Modify existing array instead of creating new one
def merge_intervals_inplace(intervals):
    intervals.sort(key=lambda x: x[0])
    
    i = 0
    while i < len(intervals) - 1:
        if intervals[i][1] >= intervals[i+1][0]:
            intervals[i][1] = max(intervals[i][1], intervals[i+1][1])
            intervals.pop(i+1)
        else:
            i += 1
    
    return intervals

Key insight: Modify in place when possible to save space.

4. Heap vs Two Pointers for Meeting Rooms II

# Two pointers: O(n log n) time, O(n) space
def min_meeting_rooms_two_pointers(intervals):
    starts = sorted([i[0] for i in intervals])
    ends = sorted([i[1] for i in intervals])
    # ... two pointer logic

# Heap: O(n log n) time, O(n) space, but cleaner
def min_meeting_rooms_heap(intervals):
    intervals.sort(key=lambda x: x[0])
    heap = []
    # ... heap logic

Key insight: Heap approach is more intuitive and easier to understand.

Common Pitfalls and Solutions

1. Incorrect Overlap Detection

# Wrong: Only checking if starts overlap
if interval1[0] <= interval2[0] <= interval1[1]:
    # This misses cases where interval2 completely contains interval1

# Correct: Check both boundaries
if interval1[0] <= interval2[1] and interval2[0] <= interval1[1]:
    # Or simpler after sorting:
if merged[-1][1] >= interval[0]:  # For sorted intervals

2. Forgetting to Sort

# Wrong: Trying to merge without sorting
def merge_intervals_wrong(intervals):
    merged = [intervals[0]]
    for interval in intervals[1:]:
        # This won't work correctly if intervals aren't sorted
        if merged[-1][1] >= interval[0]:
            merged[-1][1] = max(merged[-1][1], interval[1])

# Correct: Always sort first
def merge_intervals_correct(intervals):
    intervals.sort(key=lambda x: x[0])  # Essential step!
    merged = [intervals[0]]
    # ... rest of logic

3. Off-by-One in Merge Condition

# Wrong: Using strict inequality
if merged[-1][1] > interval[0]:  # Misses touching intervals
    # [1,2] and [2,3] should merge, but this condition fails

# Correct: Use >= for inclusive overlap
if merged[-1][1] >= interval[0]:  # Handles touching intervals
    # [1,2] and [2,3] correctly merge to [1,3]

4. Not Updating Maximum in Meeting Rooms II

# Wrong: Only tracking final count
count = 0
while p1 < len(intervals):
    if start[p1] < end[p2]:
        count += 1
    else:
        count -= 1
    p1 += 1
return count  # Wrong: This is final count, not maximum

# Correct: Track maximum during processing
count = 0
max_count = 0
while p1 < len(intervals):
    if start[p1] < end[p2]:
        count += 1
    else:
        count -= 1
    max_count = max(max_count, count)  # Track maximum
    # ... update pointers
return max_count

5. Incorrect Merge Operation

# Wrong: Only updating end time
merged[-1][1] = interval[1]  # Doesn't handle case where merged interval extends further

# Correct: Take maximum of both end times
merged[-1][1] = max(interval[1], merged[-1][1])  # Ensures full coverage

Time Complexity Comparison

Problem Complexity Summary

ProblemBrute ForceOptimizedSpaceTechnique
Merge IntervalsO(n²)O(n log n)O(n)Sort + Merge
Meeting RoomsO(n²)O(n log n)O(1)Sort + Adjacent Check
Insert IntervalO(n log n)O(n)O(n)Three-Phase Insert
Meeting Rooms IIO(n²)O(n log n)O(n)Sort + Two Pointers/Heap

Why Sorting is Essential

Without sorting:

  • Need to check all pairs: O(n²) comparisons
  • Cannot guarantee processing order
  • Complex overlap detection logic

With sorting:

  • Single pass processing: O(n) after sort
  • Guaranteed chronological order
  • Simple adjacent comparisons
  • Trade-off: O(n log n) sorting cost, but enables O(n) processing

Conclusion

Interval problems are a crucial category in algorithmic problem-solving, appearing frequently in scheduling, calendar, and resource allocation applications. Key takeaways:

Core Concepts Mastered:

Interval Fundamentals:

  • Overlap detection: a ≤ d and c ≤ b or max(a, c) ≤ min(b, d)
  • Merging operation: [min(a, c), max(b, d)] combines overlapping intervals
  • Sorting prerequisite: Most interval problems require sorting by start time
  • Chronological processing: Enables single-pass solutions

Problem Types:

  • Merge intervals: Combine overlapping ranges
  • Conflict detection: Check if intervals can coexist
  • Insertion: Add new interval with merging
  • Resource allocation: Find minimum resources needed

Essential Patterns Mastered Today:

Pattern 1: Sort-first approach (Merge Intervals, Meeting Rooms)
Pattern 2: Three-phase insertion (Insert Interval)
Pattern 3: Event-based counting (Meeting Rooms II)
Pattern 4: Adjacent comparison (Meeting Rooms)

Problem-Solving Strategies:

  • Always sort by start time for unsorted interval problems
  • Leverage sorted input to avoid unnecessary sorting
  • Use three-phase approach for interval insertion
  • Track maximum count for resource allocation problems
  • Early termination when conflict detected
  • Choose appropriate data structure (heap vs two pointers)

Optimization Principles:

  1. Sort to enable linear processing - O(n log n) sort enables O(n) solution
  2. Process in chronological order - Simplifies overlap detection
  3. Use event-based approach - For counting simultaneous events
  4. Track maximum during processing - Not just final state
  5. Merge correctly - Use max() for end times to ensure full coverage

Next Steps:

Building on interval fundamentals, future topics will cover:

  • Binary Search (can optimize interval search)
  • Sweep Line Algorithm (advanced interval problems)
  • Segment Trees (range queries and updates)
  • Greedy Algorithms (interval scheduling variations)

The problems covered here represent fundamental patterns that appear across technical interviews and real-world scheduling systems. Master these concepts, and you'll have a solid foundation for tackling more complex interval and scheduling challenges.


Practice Problems for Mastery:

Basic Interval Problems:

  • 56. Merge Intervals
    1. Insert Interval
    1. Meeting Rooms
    1. Meeting Rooms II
    1. Non-overlapping Intervals
    1. Minimum Number of Arrows to Burst Balloons

Advanced Interval Problems:

  • 759. Employee Free Time
    1. Interval List Intersections
    1. Remove Interval
    1. Remove Covered Intervals
    1. Minimum Interval to Include Each Query

References:

This comprehensive guide combines theoretical understanding with practical problem-solving, providing a solid foundation for mastering interval manipulation and scheduling algorithms.