WierdX — Programming Reference All tutorials →
Developer reference · Practical tutorials · CS fundamentals
Algorithms

Two-Pointer and Sliding Window Techniques for Array Problems

A surprising number of array and string problems that look like they need nested loops can be solved in a single pass by moving two indices toward each other or sliding a window across the data.

Published July 6, 2026

Both techniques exist to avoid the same trap: a nested loop that recomputes work it already did. If you find yourself writing for i in range(n): for j in range(n): to check every pair or every subarray, it's worth asking whether the inner loop's starting point could instead be tracked with a single variable that only moves forward.

Two pointers on a sorted array

The classic setup: given a sorted array, find two numbers that add up to a target. The brute-force approach checks every pair, which is O(n squared). With two pointers — one at the start, one at the end — you can solve it in one pass:

def two_sum_sorted(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        current = nums[left] + nums[right]
        if current == target:
            return (left, right)
        elif current < target:
            left += 1
        else:
            right -= 1
    return None

two_sum_sorted([1, 3, 5, 8, 11, 15], 19)  # (3, 4) -> 8 + 11

The reasoning is what matters, not just the code: because the array is sorted, if the current sum is too small, moving the left pointer right is the only way to increase it — moving right left could only decrease the sum further. Each pointer moves at most n times total, so the whole thing is O(n) instead of O(n squared), with O(1) extra space.

The same pattern solves "remove duplicates from a sorted array in place" and "reverse an array in place" — anywhere you need to compare or swap elements from opposite ends without allocating a new array.

Fast and slow pointers

A variant moves both pointers in the same direction at different speeds. The canonical use is cycle detection in a linked list: advance a slow pointer one step and a fast pointer two steps per iteration. If there's a cycle, the fast pointer eventually laps the slow one; if there isn't, it hits the end first.

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:
            return True
    return False

Sliding window: fixed size

Sliding window is really two pointers that define the edges of a contiguous range, expanding and shrinking as you scan. The simplest version keeps the window a fixed size — useful for "find the maximum sum of any k consecutive elements":

def max_sum_fixed_window(nums, k):
    window_sum = sum(nums[:k])
    best = window_sum
    for i in range(k, len(nums)):
        window_sum += nums[i] - nums[i - k]
        best = max(best, window_sum)
    return best

max_sum_fixed_window([2, 1, 5, 1, 3, 2], 3)  # 9 -> [5, 1, 3]

The key insight: instead of resumming all k elements every time the window moves, you subtract the element that just left and add the element that just entered. That turns an O(n*k) brute-force scan into O(n).

Sliding window: variable size

The more common interview-style problem has a window that grows and shrinks based on a condition, like "find the length of the longest substring without repeating characters":

def longest_unique_substring(s):
    seen = {}
    left = 0
    best = 0
    for right, char in enumerate(s):
        if char in seen and seen[char] >= left:
            left = seen[char] + 1
        seen[char] = right
        best = max(best, right - left + 1)
    return best

longest_unique_substring("abcabcbb")  # 3 -> "abc"

Here the right pointer always advances (the outer loop), and the left pointer only jumps forward when a repeat is found. Because left never moves backward, each character is visited by each pointer at most once, giving O(n) total despite the nested-looking logic.

How to recognize which one applies

Two pointers from opposite ends usually fits problems on sorted data where you're searching for a pair or partitioning around a pivot. Fast/slow pointers fit linked structures and cycle or midpoint problems. Sliding window fits contiguous-subarray or substring problems where you're optimizing a sum, count, or set of distinct elements within a range. If the problem statement includes the word "contiguous" or "subarray," that's usually the strongest signal to reach for a window before anything else.

None of these techniques change the asymptotic lower bound of the problem — you still have to look at every element at least once. What they eliminate is redundant re-examination of elements you've already accounted for, which is exactly the gap between O(n) and O(n squared) in practice.