AtomLearnAtomLearn
DashboardGoalsPathAchievementsReviewSign In
Algorithms & Data StructuresNot Started

Two Pointers

Walk two indices through a sequence to cut nested loops down to one pass

0%
Knowledge0%
Learn & Drill
Fluency0%
Drill & Speed
Retention0%
Mastery & Review
Confidence0%
All modes
Practice

Free tier: read the explanation here. Upgrade to Pro for Drills, Speed challenges & Mastery badges.

Upgrade
Knowledge
Fluency
Retention

Knowledge Debt detected

You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.

Explanation

replaces an O(n²) brute-force scan with a single O(n) pass by moving two indices through the data instead of one.

The three shapes this pattern takes:

  1. 1Converging — start at both ends, move inward. Used for sorted-array sum/pair problems.
  2. 2Same direction (sliding window) — both pointers move right; the gap between them is the "window." Used for substring/subarray problems.
  3. 3Fast/slow — one pointer moves 2 steps for every 1 of the other. Used for cycle detection in linked lists.
python
# Converging — find a pair in a sorted array that sums to target
def two_sum_sorted(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo < hi:
        s = arr[lo] + arr[hi]
        if s == target:
            return (lo, hi)
        elif s < target:
            lo += 1      # sum too small — need a bigger number
        else:
            hi -= 1      # sum too big — need a smaller number
    return None

Why it works: because the array is sorted, moving lo right can only increase the sum, and moving hi left can only decrease it. Every step eliminates exactly the pairs that can't possibly work — no pair is ever re-checked.

python
# Sliding window — longest substring with no repeated characters
def longest_unique_substring(s):
    seen = set()
    left = 0
    best = 0
    for right in range(len(s)):
        while s[right] in seen:        # shrink from the left until valid again
            seen.remove(s[left])
            left += 1
        seen.add(s[right])
        best = max(best, right - left + 1)
    return best

The tell: if a brute-force solution checks every pair (for i ... for j in range(i+1, n)) over a sorted array, a linked list, or a contiguous substring/subarray, two pointers usually turns it from O(n²) into O(n).

Examples

Removing duplicates in place (converging)

Two pointers in the same direction — one reads, one writes — dedupes a sorted array in O(n) with O(1) extra space

def remove_duplicates(arr):
    if not arr:
        return 0
    write = 1                      # next slot to write a unique value
    for read in range(1, len(arr)):
        if arr[read] != arr[write - 1]:
            arr[write] = arr[read]
            write += 1
    return write                   # length of the deduplicated prefix

arr = [1, 1, 2, 2, 2, 3, 4, 4]
length = remove_duplicates(arr)
print(arr[:length])  # [1, 2, 3, 4]

Fast/slow pointers — detecting a cycle

Floyd's cycle detection — if there's a loop, the fast pointer always laps the slow one

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next        # moves 1 step
        fast = fast.next.next   # moves 2 steps
        if slow is fast:
            return True          # they met — there's a cycle
    return False                 # fast hit the end — no cycle

How well did you understand this?

Next in Algorithms & Data Structures

Depth-First Search (DFS)

Continue