Python • Problem-Solving Patterns

Two Pointers & Sliding Window — Use two pointers and sliding window patterns in Python (intro)

Master two elegant techniques that turn brute-force loops into lightning-fast solutions.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 8 figures

SECTION 1

Historical Context & Motivation

Before modern coding interviews and algorithm competitions existed, programmers faced a recurring challenge: how do you efficiently search through or manipulate a sequence of data without checking every possible combination? Early computer scientists quickly realized that brute-force nested loops — checking every pair or every sub-sequence — were painfully slow for large datasets. The two pointers and sliding window techniques emerged as clever shortcuts that exploit the structure of sorted or sequential data to skip unnecessary work.

1945 • Merge Sort & Pointer Thinking

John von Neumann's merge sort algorithm used two index positions to merge sorted halves — one of the earliest uses of a two-pointer-like strategy in computing.

1960s • Binary Search Refinements

As binary search became standard, the idea of narrowing a search range with a low pointer and a high pointer became a fundamental algorithmic building block.

1970s • Network Packet Analysis

Engineers analyzing network traffic streams needed to examine fixed-size or variable-size windows of data in real time, giving rise to the formal sliding window concept.

2000s • Competitive Programming Boom

Platforms like TopCoder and later LeetCode popularized two pointers and sliding window as named problem-solving patterns, making them essential study topics for aspiring developers.

The core question these patterns address is straightforward: given a list or string, how can you examine sub-sections of it in O(n) linear time instead of the much slower O(n²) or O(n³) time that nested loops would require? The answer lies in being strategic about which elements you look at next, rather than re-examining elements you have already seen.

SECTION 2

Core Principles & Definitions

Both two pointers and sliding window rely on the same fundamental insight: instead of starting fresh with every iteration, you reuse information from the previous step. A pointer is simply a variable that stores an index position in a list or string. By moving these pointers intelligently, you avoid redundant comparisons.

1

Two Pointers (Opposite Direction)

Place one pointer at the start and one at the end of a sorted list. Move them toward each other based on a condition, narrowing the search space with each step.
2

Two Pointers (Same Direction)

Both pointers start at the beginning and move forward. A "slow" pointer tracks a result while a "fast" pointer scouts ahead, useful for removing duplicates or partitioning.
3

Fixed-Size Sliding Window

Maintain a window of exactly k elements. Slide it one position at a time by adding the next element and removing the trailing one — no need to re-sum the entire window.
4

Variable-Size Sliding Window

Expand the window by advancing the right pointer until a condition breaks, then shrink it by advancing the left pointer. Finds the shortest or longest valid sub-array.
5

Monotonic Invariant

Both patterns work because the pointers only ever move forward (never backward). This guarantees each element is visited at most a constant number of times, keeping the runtime linear.
✦

KEY TAKEAWAY

Think of two pointers like two friends searching for a book on a single shelf. Instead of both starting at the left end (brute force), one starts at the left and one at the right. They call out titles and walk toward each other, cutting the search time roughly in half at each step. The sliding window is like looking through a picture frame that you slide across a panoramic photo — you only need to notice what enters and exits the frame, not re-examine everything inside it.

SECTION 3

Visual Explanation — Two Pointers in Action

The diagram below shows how two pointers solve a classic problem: given a sorted list, find two numbers that add up to a target sum. The left pointer begins at index 0 and the right pointer begins at the last index. If the current sum is too small, the left pointer moves right to increase it; if the sum is too large, the right pointer moves left to decrease it.

Two Pointers — Finding a Pair with Target Sum = 11STEP 1: left=0, right=6 → sum = 1+10 = 11 ✓ Found!12457910L ▲R ▲ALTERNATE: target = 9 → need multiple steps12457910LR1+10=11 > 9Sum too large → move R left12457910LR1+9=10 > 9Continue: move R left again → 1+7=8 < 9, so move L right → 2+7=9 ✓ Found!
The left pointer (L) starts at the smallest element and the right pointer (R) starts at the largest. When the sum exceeds the target, R moves left; when the sum is too small, L moves right. Each pointer moves at most n times total, giving O(n) performance.

Notice how neither pointer ever moves backward. This is the key guarantee that keeps the algorithm efficient. In the worst case, the left pointer visits every element once moving rightward, and the right pointer visits every element once moving leftward. That means at most n + n = 2n comparisons total, which simplifies to O(n) time complexity — a massive improvement over the O(n²) brute-force approach that checks every possible pair.

SECTION 4

How It Works — Complexity & Code Templates

Understanding time complexity is essential for appreciating why these patterns matter. Let's compare the brute-force approach to the optimized versions and then look at Python code templates you can memorize and reuse.

BRUTE-FORCE TWO-SUM

T(n)=n×(n−1)/2≈O(n2)T(n) = n × (n − 1) / 2 ≈ O(n²)

With nested loops, you check every pair. For n = 10,000 elements, that's roughly 50 million comparisons.

TWO POINTERS PAIR SEARCH

T(n)=O(n)T(n) = O(n)

Each pointer moves at most n positions. For n = 10,000, that's at most 20,000 comparisons — 2,500× faster than brute force.

SLIDING WINDOW SUM

windowsum=windowsum−arr[i]+arr[i+k]window_sum = window_sum − arr[i] + arr[i + k]

Instead of re-summing k elements each time (O(n × k)), subtract the element leaving the window and add the element entering. Total work: O(n).

Python Template — Two Pointers (Opposite Direction)

Here is a reusable code template for the opposite-direction two-pointer pattern. The list must be sorted for this approach to work correctly.

💡

Code Template

def two_pointer_pair(arr, target): left, right = 0, len(arr) - 1 while left < right: current_sum = arr[left] + arr[right] if current_sum == target: return (left, right) elif current_sum < target: left += 1 # need a bigger sum else: right -= 1 # need a smaller sum return None # no pair found

Python Template — Fixed-Size Sliding Window

💡

Code Template

def max_sum_subarray(arr, k): window_sum = sum(arr[:k]) # sum of first window best = window_sum for i in range(k, len(arr)): window_sum += arr[i] - arr[i - k] # slide best = max(best, window_sum) return best

SECTION 5

Pattern Classification & Sliding Window Visualized

It helps to see the family of problems that each pattern solves. The table below maps common problem types to the correct pattern, and the diagram afterward shows the sliding window technique in motion.

Problem TypePatternRequirement
Find pair with target sumTwo Pointers (opposite)Sorted array
Remove duplicates in-placeTwo Pointers (same direction)Sorted array
Check if string is a palindromeTwo Pointers (opposite)String or list
Max sum of k consecutive elementsFixed Sliding WindowArray, given k
Longest substring without repeatsVariable Sliding WindowString
Smallest sub-array with sum ≥ SVariable Sliding WindowArray of positive numbers

Common problems mapped to the appropriate pattern

Fixed Sliding Window — Max Sum of k=3 Consecutive ElementsArray: [2, 1, 5, 1, 3, 2]Window 1: indices 0–2, sum = 8215132sum = 8Window 2: slide right → subtract 2, add 1 → sum = 7215132sum = 7Window 3: slide right → subtract 1, add 3 → sum = 9 ★ Best!215132sum = 9 ★Window 4: slide right → subtract 5, add 2 → sum = 6215132sum = 6Result: maximum window sum = 9 (window at indices 2–4)
The sliding window moves one position to the right each step. Rather than re-summing all three elements, we subtract the element that exits on the left and add the element that enters on the right. The best sum of 9 is found at window 3.

The key insight is that each slide performs exactly two operations — one subtraction and one addition — regardless of the window size k. Even if k were 1,000, each slide would still take constant time O(1), making the total algorithm O(n).

SECTION 6

Worked Example — Longest Substring Without Repeating Characters

Let's walk through a classic variable-size sliding window problem. Given the string "abcabcbb", find the length of the longest substring that contains no repeating characters.

Longest Substring Without Repeating Characters

  1. 1

    Step 1 — Set Up Variables

    Create two pointers: left = 0 and right = 0. Also create an empty set called char_set to track which characters are currently in the window, and a variable max_len = 0 to store the best result found so far.
    left=0, right=0, char_set={}, max_len=0
  2. 2

    Step 2 — Expand the Window

    Move right forward one position at a time. At each step, check if s[right] is already in char_set. If not, add it and update max_len. For "abcabcbb", the window expands to include "abc" (indices 0–2) without any repeats.
    char_set={'a','b','c'}, max_len=3
  3. 3

    Step 3 — Shrink When a Repeat Is Found

    When right reaches index 3, s[3] = 'a' is already in the set. Now we shrink the window from the left: remove s[left] from char_set and increment left until the duplicate is gone. After removing 'a' at index 0, left becomes 1 and we can safely add 'a' back.
    Window is now s[1:4] = 'bca', char_set={'b','c','a'}
  4. 4

    Step 4 — Continue Sliding

    Repeat the expand-and-shrink process. The window slides through "bca", "cab", "abc", and so on. Each time a duplicate is encountered, the left pointer advances until the window is valid again. The maximum window size seen is always tracked.
    max_len stays at 3 throughout
  5. 5

    Step 5 — Return the Answer

    When right has passed every character in the string, the algorithm stops. The answer is the final value of max_len. Both pointers moved at most n steps total, so the time complexity is O(n).
    Answer: 3 (the substring "abc")
💡

Full Python Code

def length_of_longest_substring(s): char_set = set() left = 0 max_len = 0 for right in range(len(s)): while s[right] in char_set: char_set.remove(s[left]) left += 1 char_set.add(s[right]) max_len = max(max_len, right - left + 1) return max_len print(length_of_longest_substring("abcabcbb")) # Output: 3

SECTION 7

Strengths, Limitations & When to Use Each Pattern

Two pointers and sliding window are powerful, but they are not universal solutions. Understanding when each technique applies — and when it does not — is just as important as knowing how to code them.

AspectTwo PointersSliding Window
Best forPair/triplet searches in sorted data, palindrome checks, partitioningSub-array / substring aggregation (sum, count, distinct elements)
Typical complexityO(n) or O(n log n) if sorting is required firstO(n) for fixed window; O(n) amortized for variable window
Pre-conditionOften requires sorted input; opposite-direction variant always doesElements must be contiguous (sub-array or substring)
LimitationCannot handle non-contiguous sub-sequences or unsorted pair searches without hashingDoes not work when elements can be skipped (non-contiguous selections)
SpaceO(1) extra — just two integer variablesO(1) to O(k) depending on what you track in the window

Comparison of Two Pointers vs. Sliding Window

🎯

WHEN TO RECOGNIZE THE PATTERN

Ask yourself two questions when you see a problem: (1) Does it involve a sorted sequence or two ends converging? That's a two-pointer clue. (2) Does it ask about a contiguous chunk — a sub-array or substring — with some aggregate property (max sum, no repeats, at most k distinct items)? That's a sliding window clue. If the answer to both is no, you likely need a different technique like hashing or dynamic programming.

SECTION 8

Connection to Advanced Techniques

The two pointers and sliding window patterns you have learned here are stepping stones to more sophisticated algorithms. As you advance in computer science, you will encounter variations that build directly on these foundations.

Intro ConceptAdvanced ExtensionWhat It Adds
Two Pointers (pair sum)Three-Sum / k-Sum problemsNest two-pointer inside a loop to find triplets or k-tuples summing to a target
Fixed Sliding WindowSliding Window with Deque (Monotonic Queue)Tracks the window maximum/minimum in O(1) per slide using a double-ended queue
Variable Sliding WindowSliding Window + HashMapUses a frequency map to handle constraints like "at most k distinct characters"
Same-direction Two PointersFast & Slow Pointers (Floyd's Cycle Detection)Detects cycles in linked lists by having one pointer move twice as fast as the other

From introductory patterns to advanced algorithms

The unifying theme across all of these is pointer monotonicity: pointers move in a single direction, ensuring each element is processed a bounded number of times. Once you internalize this principle, you will start recognizing opportunities to apply it in problems that at first glance seem to require brute force. In future lessons, you will also see how combining sliding window with hash maps enables you to solve string-matching problems that would otherwise require exponential time.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain in your own words why the opposite-direction two-pointer technique requires the input to be sorted. What would go wrong if the list were unsorted?
Show answer
The technique relies on a predictable relationship between pointer movement and the running sum. In a sorted array, moving the left pointer right always increases the sum, and moving the right pointer left always decreases it. This monotonic behavior lets you decide which pointer to move based on whether the current sum is too large or too small. If the array were unsorted, moving a pointer in one direction could either increase or decrease the sum unpredictably, so you could skip valid pairs without ever evaluating them. In that case, you would need a hash set approach instead.

Question 2: BASIC CALCULATION

Given the sorted list [1, 3, 5, 7, 9, 11] and a target sum of 12, trace through the two-pointer algorithm step by step. How many comparisons are needed before finding the pair?
Show answer
Start with left=0 (value 1) and right=5 (value 11). Sum = 12 → match found on the very first comparison! The pair is (1, 11) at indices (0, 5). Only 1 comparison was needed. If the target were 10, the trace would be: 1+11=12 > 10 → move right to index 4 → 1+9=10 → found in 2 comparisons. The worst case for this six-element list would be at most 5 comparisons, since one pointer moves each step and they must meet before crossing.

Question 3: INTERMEDIATE

Write a Python function max_avg_subarray(arr, k) that uses the fixed-size sliding window pattern to find the maximum average of any contiguous sub-array of length k. Test it with arr = [1, 12, -5, -6, 50, 3] and k = 4. What is the answer?
Show answer
The function initializes the window sum as sum(arr[:4]) = 1+12+(−5)+(−6) = 2. Then it slides: remove 1, add 50 → sum = 51; remove 12, add 3 → sum = 42. The maximum window sum is 51, so the maximum average is 51/4 = 12.75. Here is the code: def max_avg_subarray(arr, k): window_sum = sum(arr[:k]) best = window_sum for i in range(k, len(arr)): window_sum += arr[i] - arr[i - k] best = max(best, window_sum) return best / k This runs in O(n) time because each element is added and removed from the window exactly once.

Question 4: APPLIED

A teacher records daily quiz scores for a student over 10 days: [65, 70, 80, 85, 90, 75, 60, 95, 88, 72]. Write a Python function using a sliding window to find the best 5-day streak (the 5 consecutive days with the highest total score). Return both the starting index and the total.
Show answer
Using the fixed sliding window: initial window (indices 0–4) sums to 65+70+80+85+90 = 390. Slide right: 390−65+75 = 400 (indices 1–5). Slide: 400−70+60 = 390 (indices 2–6). Slide: 390−80+95 = 405 (indices 3–7). Slide: 405−85+88 = 408 (indices 4–8). Slide: 408−90+72 = 390 (indices 5–9). The best 5-day streak starts at index 4 with a total score of 408 (days with scores 90, 75, 60, 95, 88). The function can be written as: def best_streak(scores, k): ws = sum(scores[:k]) best, start = ws, 0 for i in range(k, len(scores)): ws += scores[i] - scores[i - k] if ws > best: best, start = ws, i - k + 1 return start, best

Question 5: CRITICAL THINKING

Consider the problem: given a string, find the length of the longest substring that contains at most 2 distinct characters. Explain which pattern applies, outline the algorithm in pseudocode, and analyze its time and space complexity. Test your logic on the string "eceba".
Show answer
This is a variable-size sliding window problem because we are looking for the longest contiguous substring meeting a constraint. Use a dictionary to count character frequencies in the window. Expand right to add characters; when the dictionary has more than 2 distinct keys, shrink from the left, decrementing counts and removing keys that reach 0. Track max_len throughout. Pseudocode: left = 0, freq = {}, max_len = 0 for right in range(len(s)): freq[s[right]] = freq.get(s[right], 0) + 1 while len(freq) > 2: freq[s[left]] -= 1 if freq[s[left]] == 0: del freq[s[left]] left += 1 max_len = max(max_len, right - left + 1) For "eceba": window grows to "ece" (2 distinct), then "eceb" (3 distinct) → shrink to "ceb" (3) → shrink to "eb" (2). Continue to "eba" (3) → shrink to "ba" (2). Maximum length is 3 ("ece"). Time: O(n) since each pointer moves at most n times. Space: O(1) because the dictionary holds at most 3 entries before shrinking.

SUMMARY

Lesson Summary

Summary

The two pointers pattern uses two index variables that move through a sequence — either from opposite ends converging toward each other (ideal for pair searches in sorted arrays and palindrome checks) or in the same direction (ideal for removing duplicates or partitioning). The sliding window pattern maintains a contiguous sub-section of the data: a fixed-size window slides by removing one element and adding another, while a variable-size window expands and contracts to find the longest or shortest valid sub-array or substring.

Both patterns achieve O(n) linear time by ensuring pointers only move forward (pointer monotonicity), which eliminates the redundant work of nested loops. To identify which pattern to use, look for keywords in the problem: "pair," "sorted," and "palindrome" point to two pointers, while "contiguous," "sub-array," "substring," and "window" point to sliding window. These patterns form the foundation for advanced techniques like three-sum, monotonic queues, and Floyd's cycle detection.

Varsity Tutors • Python • Two Pointers & Sliding Window