1. Interval / Scheduling

Pattern: Sort by end time, greedily keep non-overlapping intervals.

When to recognize: Problem involves intervals, meetings, tasks with start/end times.

intervals.sort(key=lambda x: x[1])  # sort by end time
prev_end = float('-inf')
for start, end in intervals:
    if start >= prev_end:
        prev_end = end  # keep it
    else:
        count += 1  # discard it

2. Two Pointer Greedy

Pattern: Start pointers at both ends, move the "worse" side inward.

When to recognize: Maximize/minimize something involving pairs from opposite ends.

left, right = 0, len(arr) - 1
while left < right:
    result = max(result, some_function(arr[left], arr[right]))
    if arr[left] <= arr[right]:
        left += 1
    else:
        right -= 1

3. Range / Reach Greedy

Pattern: Track the furthest reachable index, jump only when forced.

When to recognize: Can you reach the end? Minimum jumps to reach end?

farthest = 0
curr_end = 0
jumps = 0
for i in range(len(nums) - 1): 
    farthest = max(farthest, i + nums[i])
    if i == curr_end:
        jumps += 1
        curr_end = farthest

4. Sorting + Greedy

Pattern: Sort by some key that makes the greedy choice obvious, then iterate.

When to recognize: Optimization problem where ordering matters.

# sort by whatever makes the greedy choice locally optimal
items.sort(key=lambda x: x[0])
for item in items:
    # make greedy choice