Two Pointers & Sliding Window

You've now covered every core data structure and traversal technique in this track. The next four lessons cover patterns — reusable strategies that combine those building blocks to solve entire families of problems efficiently. We'll start with two of the most common: the two pointers technique and its close cousin, the sliding window. Both trade a naive O(n²) nested loop for a single O(n) pass by moving one or two indices through a list intelligently instead of re-scanning it from scratch.

Two Pointers: The Idea

The two pointers pattern uses two indices that move through a sequence — usually toward each other, sometimes in the same direction — instead of a nested loop. It's a strong signal to look for whenever a problem involves a sorted list and asks about a pair, or a triple, of elements satisfying some condition (a sum, a difference, a product).

Worked Example: Two-Sum on a Sorted List

Recall the "Two Sum" problem from the hashing lesson: given a list and a target, find two indices whose values sum to the target. The dict solution there ran in O(n) time and O(n) space and worked on an unsorted list. If the list is already sorted, two pointers solves it in O(n) time but only O(1) extra space — no dict needed at all.

The idea: start one pointer left at index 0 and another right at the last index. If the sum of the two values is too small, the only way to increase it is to move left forward (since the list is sorted, that guarantees a larger or equal value). If the sum is too large, move right backward. The pointers converge toward each other, and every step eliminates one candidate pair for good — that's what keeps it linear.

# nums is sorted ascending. Returns the indices of the pair, or None if none exists.
def two_sum_sorted(nums, target):
    left, right = 0, len(nums) - 1

    while left < right:
        total = nums[left] + nums[right]
        if total == target:
            return [left, right]
        elif total < target:
            left += 1   # need a bigger sum
        else:
            right -= 1  # need a smaller sum

    return None  # no pair found

Time complexity: O(n) — the two pointers together cover at most n steps before they meet; no element is ever revisited. Space complexity: O(1) — only two index variables, no auxiliary structure.

Trade-off to say out loud in an interview: the dict version works on unsorted input in O(n) time but costs O(n) space. The two-pointer version needs sorted input (sorting costs O(n log n) if it isn't already sorted) but then runs with O(1) space. If the list is already sorted, two pointers wins outright. If it isn't, and you'd have to sort it just for this, the dict approach is usually simpler and no worse asymptotically.

Sliding Window: The Idea

A sliding window is two pointers that define a contiguous range ("the window") which expands and shrinks as it moves across a list or string, instead of two pointers converging from opposite ends. It's the pattern to reach for whenever a problem asks about a contiguous sublist or substring — "maximum/minimum sum of a subarray of size K," "longest substring with property X," "smallest subarray with sum ≥ target."

Worked Example: Maximum Sum Subarray of Size K (Fixed Window)

Brute force recomputes the sum of every window of size K from scratch: O(n·K) time. A sliding window instead keeps a running sum, adding the element entering the window on the right and subtracting the element leaving it on the left, so each slide is O(1) instead of O(K).

def max_sum_subarray_of_size_k(nums, k):
    window_sum = sum(nums[:k])  # build the first window
    max_sum = window_sum

    for end in range(k, len(nums)):
        window_sum += nums[end] - nums[end - k]  # slide: add right, drop left
        max_sum = max(max_sum, window_sum)

    return max_sum

Time complexity: O(n) — building the first window with sum(nums[:k]) costs O(k), then every subsequent slide is O(1), for a total of O(k + (n − k)) = O(n). Space complexity: O(1) — a running sum and a max variable, nothing that grows with input size.

Worked Example: Longest Substring Without Repeating Characters (Variable Window)

This is the more advanced sliding window shape: the window's size isn't fixed — it grows while a condition holds and shrinks when it's violated. Here the condition is "no repeated characters inside the window." We track the characters currently in the window with a set, expand right one character at a time, and whenever we'd introduce a duplicate, shrink from left until the duplicate is gone.

def length_of_longest_substring(s):
    window_chars = set()
    left = 0
    max_length = 0

    for right in range(len(s)):
        c = s[right]

        # shrink from the left until c is no longer a duplicate in the window
        while c in window_chars:
            window_chars.remove(s[left])
            left += 1

        window_chars.add(c)
        max_length = max(max_length, right - left + 1)

    return max_length

Time complexity: O(n) — although there's a nested while loop, left only ever moves forward and never resets, so across the whole run it advances at most n times total; combined with right's n steps, the total work is O(n), not O(n²). Space complexity: O(min(n, m)) where m is the size of the character set — the set holds at most one entry per distinct character currently in the window.

The two-pointers-move-forward-only argument is the standard way to justify O(n) for a variable sliding window in an interview, even though the code visually has a loop inside a loop. Say it explicitly — interviewers often ask "isn't this O(n²)?" specifically to see if you can defend the bound.

Recognizing Which Pattern Applies

Clue in the problem statementLikely pattern
"sorted list," find a pair/triple summing to XTwo pointers (converging from both ends)
"contiguous subarray/substring of size K"Sliding window, fixed size
"longest/shortest contiguous subarray/substring satisfying condition"Sliding window, variable size (expand/shrink)
Comparing elements from both ends of a structure (palindrome check, container with most water)Two pointers, same-direction or converging
"remove duplicates in-place," "partition a list"Two pointers, same direction (fast/slow)
Next: Patterns aren't just about avoiding nested loops — sometimes the nested loop itself is the right idea, but you can avoid recomputing the same subproblem twice. That's dynamic programming, starting with the 1D case in Dynamic Programming (1D).