Algorithms

Two Pointers Algorithm Explained

Learn the two pointers algorithm pattern with Java examples for sorted arrays, pairs, partitions, linked lists, complexity, and common mistakes.

Two pointers is an algorithm pattern that uses two positions to scan, compare, partition, or search a data structure more efficiently than nested loops.

It is most common with arrays, strings, and linked lists. It often turns an O(n^2) brute-force search into an O(n) or O(n log n) workflow, depending on whether sorting is needed first.

Quick answer

The two pointers pattern keeps two indexes or references and moves them according to a rule.

Common pointer styles include:

  • One pointer at the start and one at the end.
  • A slow pointer and a fast pointer.
  • A read pointer and a write pointer.
  • Two pointers scanning two sorted arrays.
  • A left and right pointer maintaining a window.

Use two pointers when the next move can be decided from the current pair or boundary state.

Start-end pointers on a sorted array

A classic problem is finding whether a sorted array contains two numbers that add up to a target.

Brute force checks every pair:

for each i:
  for each j:
    check nums[i] + nums[j]

That is O(n^2).

With sorted input, two pointers can do it in O(n):

public static boolean hasPairWithSum(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1;

    while (left < right) {
        int sum = nums[left] + nums[right];

        if (sum == target) {
            return true;
        }

        if (sum < target) {
            left++;
        } else {
            right--;
        }
    }

    return false;
}

If the sum is too small, moving left right increases the sum. If the sum is too large, moving right left decreases it. That logic depends on the array being sorted.

Why sorted input matters

Two pointers works best when pointer movement is meaningful.

In the pair-sum example, sorted order gives a clear rule:

Current sumMove
Too smallIncrease left
Too largeDecrease right
EqualFound a pair

Without sorted order, moving left or right does not give predictable information. You may need a hash set instead.

Read Time Complexity of Java Collections for how hash sets and hash maps can replace repeated scans when ordering is not available.

Read-write pointers

Another common form uses one pointer to read input and another pointer to write the compacted result.

Example: remove duplicates from a sorted array in place.

public static int removeDuplicates(int[] nums) {
    if (nums.length == 0) {
        return 0;
    }

    int write = 1;

    for (int read = 1; read < nums.length; read++) {
        if (nums[read] != nums[read - 1]) {
            nums[write] = nums[read];
            write++;
        }
    }

    return write;
}

After this method returns, the first write elements contain the unique values.

This pattern appears in compaction, filtering, deduplication, and stream-like transformations.

Fast and slow pointers

Fast and slow pointers are useful when one pointer should move faster than the other.

Common uses:

  • Detect a cycle in a linked list.
  • Find the middle of a linked list.
  • Remove the nth node from the end.
  • Compare positions at different speeds.

Example shape:

ListNode slow = head;
ListNode fast = head;

while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
}

When the loop ends, slow is near the middle of the list.

Merging two sorted arrays

Two pointers are also useful when scanning two sorted inputs.

Example: merge sorted arrays into a new result.

public static int[] mergeSorted(int[] a, int[] b) {
    int[] result = new int[a.length + b.length];
    int i = 0;
    int j = 0;
    int out = 0;

    while (i < a.length && j < b.length) {
        if (a[i] <= b[j]) {
            result[out++] = a[i++];
        } else {
            result[out++] = b[j++];
        }
    }

    while (i < a.length) {
        result[out++] = a[i++];
    }

    while (j < b.length) {
        result[out++] = b[j++];
    }

    return result;
}

This is O(n + m), where n and m are the input lengths.

Two pointers vs sliding window

Sliding window is a related pattern, but it has a narrower purpose.

PatternTypical goal
Two pointersSearch, compare, merge, partition, or detect structure
Sliding windowMaintain a contiguous range with changing state

If the problem asks for a longest substring, minimum subarray, or rolling count, read Sliding Window Algorithm Explained. If it asks for pairs, sorted scans, deduplication, merging, or list cycles, two pointers is often the broader mental model.

Complexity

Two pointers often produce a single-pass solution.

Problem typeTypical complexity
Pair search in sorted arrayO(n)
Merge two sorted arraysO(n + m)
In-place compactionO(n)
Linked list middle or cycle checkO(n)
Sort first, then two pointersO(n log n)

If the algorithm sorts first, include the sorting cost in the final complexity.

Read Big-O Notation Explained when comparing O(n), O(n log n), and O(n^2) approaches.

Backend-style examples

Two pointers are not only for interview tasks.

Backend-flavored examples include:

  • Merging two sorted event streams.
  • Comparing old and new sorted ID lists.
  • Deduplicating sorted exports.
  • Finding overlapping time ranges.
  • Compacting an in-memory result before serialization.
  • Walking two paginated result sets during a migration.

For text or config comparisons, a purpose-built diff is usually better than writing your own pointer logic. Use the Text Diff Checker when comparing snippets manually.

Common mistakes

The first mistake is using start-end pointers on unsorted input without a movement rule.

The second mistake is forgetting to move a pointer after a match, which can cause an infinite loop.

The third mistake is skipping duplicates incorrectly. If the result should contain unique pairs, duplicate handling belongs immediately after recording a match.

The fourth mistake is ignoring the cost of sorting. A solution that sorts first is usually O(n log n), not O(n).

The fifth mistake is moving both pointers when only one should move. The movement rule should come from the problem condition, not from habit.

Practical recommendation

When considering two pointers, write the movement rule in plain English first:

If the sum is too small, move left right.
If the sum is too large, move right left.
If values match, record and move both.

If you cannot explain why a pointer move is safe, the pattern may not apply yet.

Read Sliding Window Algorithm Explained for contiguous range problems and Prefix Sum Algorithm Explained for repeated range queries.

For complexity fundamentals, read Big-O Notation Explained, Binary Search Explained, and Time Complexity of Java Collections. For the full sequence, browse the Algorithms and Complexity Learning Path and the Topics map.