How to Apply the Two Pointers Technique for Array Problems: 3 Patterns Explained

The two pointers technique uses two indices to traverse an array in a single pass, achieving O(N) time complexity and O(1) space complexity by inspecting each element at most once.

This algorithmic framework is extensively documented in the azl397985856/leetcode repository, where it serves as the foundation for solving hundreds of array and string problems. Whether you are searching for pairs, detecting cycles, or optimizing subarray calculations, understanding how to apply the two pointers technique for array problems will significantly reduce your solution's memory footprint and improve runtime performance.

What Is the Two Pointers Technique?

The two pointers technique is a strategy that maintains two references (indices) into a data structure simultaneously. Unlike brute-force approaches that use nested loops creating O(N²) complexity, this method moves one or both pointers monotonically toward a solution.

Because arrays offer O(1) random access, the technique is particularly effective for array problems. The pointers can jump to any position instantly, and by moving them in a coordinated fashion—either toward each other, at different speeds, or maintaining a fixed distance—you can encode problem-specific invariants directly into the traversal logic.

Three Canonical Patterns for Array Problems

According to the source documentation in 91/two-pointers.md, nearly every array problem solvable with two pointers falls into one of three distinct patterns.

Fast and Slow Pointers (Tortoise and Hare)

In this pattern, one pointer advances one step per iteration while the other advances two steps (or a larger fixed step). This creates a relative speed differential that reveals structural properties of the data.

Typical use cases:

  • Detecting cycles in linked lists or arrays
  • Finding the middle element of a sequence
  • Locating the k-th element from the end

Implementation example: See problems/141.Linked-List-Cycle.md for the classic cycle detection algorithm.

Left and Right Pointers (Two Ends)

Here, pointers initialize at opposite boundaries (left = 0, right = n - 1) and move toward each other based on comparison logic. This pattern exploits sorted order or symmetric properties.

Typical use cases:

  • Binary search on sorted arrays
  • Two-sum problems on sorted data
  • Reversing arrays or checking palindromes

Implementation example: The problems/977.Squares-of-a-Sorted-Array.md solution demonstrates this pattern effectively.

Fixed-Distance Sliding Window

This pattern maintains pointers with a constant offset or expands/contracts a window to satisfy constraints. Both pointers typically move in the same direction (left to right), with the right pointer expanding the window and the left pointer contracting it when constraints are violated.

Typical use cases:

  • Finding longest/shortest subarrays with specific sums
  • String problems involving character constraints (e.g., longest substring with k distinct characters)
  • Removing duplicates from sorted arrays

Implementation example: See problems/80.remove-duplicates-from-sorted-array-ii.md for a sliding window approach to in-place array modification.

Practical Implementation Examples

The following code examples from the repository demonstrate each pattern in production-ready implementations.

Two Sum on a Sorted Array (Left-Right Pattern)

This Python implementation from the repository's two-pointer documentation solves the classic two-sum problem on sorted data using the left-right pattern:

def two_sum_sorted(nums, target):
    """Return indices (0-based) of two numbers that add up to target.
    Precondition: `nums` is sorted."""
    l, r = 0, len(nums) - 1
    while l < r:
        s = nums[l] + nums[r]
        if s == target:
            return l, r
        elif s < target:
            l += 1          # need a larger sum → move left pointer right

        else:
            r -= 1          # need a smaller sum → move right pointer left

    return None           # no solution

The algorithm initializes pointers at both ends and moves them inward based on the sum comparison, guaranteeing O(N) time with O(1) auxiliary space.

Longest Substring with K Distinct Characters (Sliding Window)

This JavaScript implementation demonstrates the fixed-distance (expanding/contracting) sliding window pattern for string problems:

function longestSubstrKDistinct(s, k) {
    const map = new Map();         // char → count in current window
    let l = 0, maxLen = 0;

    for (let r = 0; r < s.length; r++) {
        map.set(s[r], (map.get(s[r]) ?? 0) + 1);
        // shrink window until we have at most k distinct chars
        while (map.size > k) {
            const cnt = map.get(s[l]) - 1;
            if (cnt === 0) map.delete(s[l]);
            else map.set(s[l], cnt);
            l++;
        }
        maxLen = Math.max(maxLen, r - l + 1);
    }
    return maxLen;
}

The right pointer (r) expands the window to include new characters, while the left pointer (l) contracts it when the constraint (at most k distinct characters) is violated.

Detecting a Cycle in a Linked List (Fast/Slow Pattern)

This Java implementation from problems/141.Linked-List-Cycle.md uses the tortoise-and-hare algorithm to detect cycles with O(1) space:

public boolean hasCycle(ListNode head) {
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;          // move 1 step
        fast = fast.next.next;     // move 2 steps
        if (slow == fast) return true; // meeting point → cycle
    }
    return false;
}

If a cycle exists, the fast pointer (moving two steps) will eventually meet the slow pointer (moving one step) inside the cycle. If the fast pointer reaches the end, no cycle exists.

Key Repository Files and Resources

The azl397985856/leetcode repository contains extensive documentation and implementations of these patterns:

Summary

  • The two pointers technique reduces array problems from O(N²) brute-force to O(N) time while maintaining O(1) space complexity.
  • Three canonical patterns cover most scenarios: Fast/Slow for cycles and middle-finding, Left-Right for sorted arrays and two-sum problems, and Fixed-Distance Sliding Window for substring/subarray constraints.
  • Implementation requires identifying the invariant (what condition must hold), initializing pointers according to the chosen pattern, and moving them monotonically toward the solution.
  • The azl397985856/leetcode repository provides production-ready templates in 91/two-pointers.md and worked examples across multiple problem files.

Frequently Asked Questions

What is the time and space complexity of the two pointers technique?

The two pointers technique typically achieves O(N) time complexity because each pointer traverses the array at most once, inspecting each element a constant number of times. The space complexity is O(1) because only two index variables (and possibly a few scalar variables) are stored regardless of input size, making it an in-place algorithm.

When should I use the fast and slow pointer pattern versus the left and right pattern?

Use the fast and slow pattern when you need to detect cycles, find the middle of a sequence, or identify the k-th element from the end without knowing the total length beforehand. Use the left and right pattern when the array is sorted or when you need to find pairs that satisfy a specific condition (like two-sum), as starting at both ends allows you to eliminate one element from consideration with each comparison.

Can the two pointers technique work on unsorted arrays?

Yes, the two pointers technique works on unsorted arrays, but the left-right pattern specifically requires sorted data to guarantee correctness when moving pointers inward. For unsorted arrays, you typically use the sliding window (fixed-distance) pattern or the fast/slow pattern, neither of which depends on sorted order. If you need the left-right behavior on unsorted data, you must sort the array first (O(N log N)), then apply the technique.

How do I choose between a fixed-size window and a variable-size sliding window?

Choose a fixed-size window when the problem explicitly requires subarrays or substrings of a specific length k, allowing both pointers to advance at the same rate. Choose a variable-size (dynamic) sliding window when you need to find the longest or shortest valid subarray that satisfies a constraint (like "at most k distinct characters"), requiring the left pointer to move conditionally while the right pointer expands the window.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →