Skip to main content
Back to the DSA atlas
Linear structures Technique Intro

Two Pointers & Sliding Window

Shrink O(n²) brute force to O(n) by maintaining a window or two converging pointers. The go-to technique for subarray and subsequence problems.

arraytwo-pointerssliding-window

Interactive visualization

Use the controls to connect the idea to concrete operations before diving into the full write-up.

27111519232834
Left Right Found

Family

Linear structures

State that flows from one end to the other: lists, deques, stacks, and windows.

Builds on

Foundational

You can start here without another page first.

Unlocks

1 next topic

Use these follow-ups when you want to apply the current idea in a richer setting.

Learning paths

1

This topic appears in curated progressions so you can keep moving without guessing the next step.

Problem

Given an array or string, find a subarray/subsequence that satisfies some condition — often involving a sum, count, or character constraint — in O(n)O(n) time.

Intuition

Instead of checking all O(n2)O(n^2) pairs or subarrays, maintain two pointers (or a window boundary) that you advance based on the current state. The key insight: if advancing the right pointer makes the condition “too much,” advance the left pointer to shrink the window. Each pointer moves at most nn times → O(n)O(n) total.

Two pointers (converging)

Two pointers start at opposite ends and walk toward each other. Used when the array is sorted and you’re looking for a pair.

Two Sum (sorted array)

two_sum(arr, target):
    left ← 0
    right ← arr.length - 1

    while left < right:
        sum ← arr[left] + arr[right]
        if sum == target:
            return (left, right)
        else if sum < target:
            left += 1      // need bigger sum
        else:
            right -= 1     // need smaller sum

    return not found

Why it works: if the sum is too small, the only way to increase it is to move left forward (to a larger value). If too big, move right backward. You never need to reconsider skipped positions because the array is sorted.

Container with most water

Given heights h[0..n1]h[0..n-1], find the two lines that form a container holding the most water. Area = min(h[l],h[r])×(rl)\min(h[l], h[r]) \times (r - l).

max_area(heights):
    left ← 0, right ← heights.length - 1
    best ← 0

    while left < right:
        area ← min(heights[left], heights[right]) * (right - left)
        best ← max(best, area)

        if heights[left] < heights[right]:
            left += 1
        else:
            right -= 1

    return best

Why move the shorter side? The width decreases by 1 no matter which side you move. The only way to potentially increase the area is to find a taller line. Moving the taller side can’t help — the area is bottlenecked by the shorter side.

Sliding window (fixed size)

Maintain a window of exactly kk elements. Slide it across the array.

Maximum sum subarray of size k

max_sum_k(arr, k):
    window_sum ← sum(arr[0..k-1])
    best ← window_sum

    for i in k..arr.length-1:
        window_sum += arr[i] - arr[i - k]  // slide: add right, remove left
        best ← max(best, window_sum)

    return best

Each step adds one element and removes one — O(1)O(1) per step, O(n)O(n) total. The brute force of recomputing the sum for each window would be O(nk)O(nk).

Sliding window (variable size)

The window expands and contracts based on a condition. This is the most powerful pattern.

Longest substring without repeating characters

longest_unique_substr(s):
    seen ← {}
    left ← 0
    best ← 0

    for right in 0..s.length-1:
        while s[right] in seen:
            seen.remove(s[left])
            left += 1

        seen.add(s[right])
        best ← max(best, right - left + 1)

    return best

right advances every iteration. left advances only when needed to restore the invariant (no duplicates). Both pointers only move forward → total work is O(n)O(n).

Minimum window substring

Find the smallest window in string ss that contains all characters of string tt.

min_window(s, t):
    need ← character counts of t
    have ← 0, required ← len(need)
    left ← 0, best ← (∞, 0, 0)

    for right in 0..s.length-1:
        // Expand: add s[right]
        if s[right] in need:
            window_counts[s[right]] += 1
            if window_counts[s[right]] == need[s[right]]:
                have += 1

        // Contract: try to shrink from left
        while have == required:
            update best if current window is smaller
            if s[left] in need:
                window_counts[s[left]] -= 1
                if window_counts[s[left]] < need[s[left]]:
                    have -= 1
            left += 1

    return best

When to use which pattern

PatternWhen to useSorted needed?
Converging two pointersPair finding, triplet problemsUsually yes
Same-direction two pointersRemove duplicates, partitionSometimes
Fixed sliding windowSubarray of exact size kkNo
Variable sliding windowShortest/longest subarray with constraintNo
Fast/slow pointersLinked list cycle, middle findingN/A

The monotonic invariant

The key to correctness in sliding window problems is maintaining an invariant: the window always represents a valid (or almost-valid) state. When expanding breaks the invariant, contract until it’s restored. This ensures you don’t miss any valid windows.

Think of it as: the left pointer is the minimum left boundary for the current right pointer. You never need to move left backward because doing so would only add more elements (potentially breaking the invariant more).

Common mistakes

  • Forgetting that two pointers on unsorted arrays doesn’t work for pair-sum problems. Sort first, or use a hash map.
  • Off-by-one in window size: right - left + 1 is the window size, not right - left.
  • Not handling the contraction correctly: when you shrink the window, make sure you undo whatever the removed element contributed (e.g., decrement a count).

Complexity

PatternTimeSpace
Two pointers (converging)O(n)O(n)O(1)O(1)
Fixed sliding windowO(n)O(n)O(1)O(1) or O(k)O(k)
Variable sliding windowO(n)O(n)O(k)O(k) where kk = alphabet/unique elements

Key takeaways

  • Each pointer moves at most n times — this is what gives two-pointer techniques their O(n)O(n) guarantee, even though it might look like nested loops.
  • Sorting is a prerequisite for converging two pointers on pair-sum problems. Without sorted order, use a hash map instead.
  • Variable sliding window is the most versatile pattern — it handles shortest/longest subarray problems with any monotonic constraint.
  • Maintain an invariant as you slide — when expanding breaks it, contract from the left until it’s restored. This guarantees you never miss a valid window.
  • Off-by-one errors are the #1 bug — window size is right - left + 1, not right - left.

Practice problems

ProblemDifficultyKey idea
Two Sum II - Input Array Is Sorted🟢 EasyConverging pointers on a sorted array to find a target sum
Container With Most Water🟡 MediumMove the shorter side inward since width always decreases
3Sum🟡 MediumSort + fix one element, then use two pointers for the remaining pair
Longest Substring Without Repeating Characters🟡 MediumVariable sliding window with a set to track duplicates
Minimum Window Substring🔴 HardExpand to satisfy the constraint, then contract to minimize the window

Relation to other topics

  • Binary search: converging two pointers on a sorted array is a generalization of binary search — both exploit sorted order to eliminate candidates.
  • Hash maps: for unsorted arrays, hash maps solve pair-sum in O(n)O(n) where two pointers can’t. They’re alternative approaches to the same class of problems.
  • Monotonic queues/deques: sliding window max/min problems combine the sliding window framework with a monotonic deque for O(n)O(n) solutions.

What this enables

Once the current idea feels natural, these are the most useful next jumps.

Related directions

These topics live nearby conceptually, even if they are not direct prerequisites.

More from Linear structures

Stay in the same family when you want parallel variations of the same mental model.

Paths that include this topic

Follow one of these sequences if you want a guided next step instead of open-ended browsing.

Linear toolkit

Learn how sequence-shaped data unlocks queues, stacks, windows, and fast pointer manipulations.

From the blog

Pair the atlas with recent writing from the rest of the site when you want a broader engineering perspective alongside the topic graph.