Master the art of using two coordinated indices to solve array and string problems in O(n) time.
The two pointers technique uses two index variables that traverse a data structure simultaneously. Instead of checking every possible pair with nested loops (O(n²)), the pointers move strategically so you examine the right elements in a single pass.
Two pointers eliminate redundant comparisons. At each step, you gain enough information to discard part of the search space, allowing one or both pointers to advance.
Why does it work? Because the input has structure (usually sorted or has a monotonic property), moving one pointer gives you information about which direction the other pointer should go. This transforms a 2D search space into a 1D traversal.
Look for these recognition signals in a problem statement. If you spot one or more, the two pointers technique is likely applicable.
The input is sorted (or can be sorted without losing information). Sorted order lets pointers make informed directional choices.
Find two elements that satisfy a condition: a target sum, a specific difference, or a complementary relationship.
Check palindromes, compare characters from front and back, or validate symmetric properties.
Modify the array in-place without extra space. A slow pointer marks the write position, a fast pointer scans ahead.
Rearrange elements based on a condition (move zeros, Dutch National Flag, separate even/odd).
The problem asks for constant extra space. Two pointers work in-place, making them ideal when you cannot use auxiliary data structures.
Two pointers start at opposite ends and move inward. This is the classic approach for problems on sorted arrays where you want to find a pair that satisfies a condition.
The left pointer starts at index 0, the right pointer at the last index. At each step, evaluate the pair and decide which pointer to move based on the result.
// Opposite Direction — Template int left = 0, right = arr.length - 1; while (left < right) { int current = evaluate(arr[left], arr[right]); if (current == target) { // Found the answer return new int[]{left, right}; } else if (current < target) { left++; // Need a larger value } else { right--; // Need a smaller value } }
Given sorted array [1, 3, 5, 7, 9, 11], find two numbers that sum to 10.
Only 2 steps instead of the 15 pair comparisons brute force would require.
Use cases: Two Sum II (sorted), Container With Most Water, Valid Palindrome, Trapping Rain Water. Any problem where the sorted order lets you reason about whether to increase or decrease a value.
Both pointers start at the same end and move in the same direction, but at different speeds. The fast pointer scans through every element while the slow pointer tracks a "write" position or a boundary.
The fast pointer explores the array. When it finds an element that meets the condition, the slow pointer advances and the element is placed at that position.
// Same Direction — Template int slow = 0; for (int fast = 0; fast < arr.length; fast++) { if (condition(arr[fast])) { arr[slow] = arr[fast]; slow++; } } // slow is now the new length of the processed array
Remove duplicate values in-place, returning the new length. Keep one copy of each unique value.
Use cases: Remove Duplicates (#26), Remove Element (#27), Move Zeros (#283), and linked list cycle detection (Floyd's algorithm). The slow pointer builds the result while the fast pointer does the scanning.
The sliding window technique is a specialized form of two pointers. Both pointers move in the same direction, but they define the left and right boundaries of a window that expands and contracts.
// Sliding Window — uses two pointers as window boundaries int left = 0, sum = 0; for (int right = 0; right < arr.length; right++) { sum += arr[right]; // expand window while (sum > target) { sum -= arr[left]; // shrink window left++; } result = Math.max(result, right - left + 1); }
Think of sliding window as "two pointers with state." If you only care about individual elements at the pointer positions, use two pointers. If you need to track an aggregate (sum, count, frequency map) of everything between the pointers, use sliding window.
These are the canonical Java templates. Memorize the structure, then adapt the condition and action for each specific problem.
// Use when: sorted array, find pair, palindrome check // Invariant: answer lies within [left, right] at all times int left = 0, right = arr.length - 1; while (left < right) { // Evaluate the current pair int current = arr[left] + arr[right]; if (current == target) { // Found: process result return new int[]{left, right}; } else if (current < target) { left++; // Sum too small — need larger left value } else { right--; // Sum too large — need smaller right value } } // No valid pair found
// Use when: remove duplicates, filter elements, partition // Invariant: arr[0..slow-1] contains the processed result int slow = 0; for (int fast = 0; fast < arr.length; fast++) { if (shouldKeep(arr[fast])) { arr[slow] = arr[fast]; // Place at write position slow++; // Advance write position } } // slow = new length of the result array return slow;
// Use when: find triplets, reduce to two-pointer subproblem // Fix one element, then use opposite-direction on the rest Arrays.sort(arr); for (int i = 0; i < arr.length - 2; i++) { if (i > 0 && arr[i] == arr[i - 1]) continue; // skip duplicates int left = i + 1, right = arr.length - 1; int target = -arr[i]; while (left < right) { int sum = arr[left] + arr[right]; if (sum == target) { result.add(Arrays.asList(arr[i], arr[left], arr[right])); while (left < right && arr[left] == arr[left+1]) left++; while (left < right && arr[right] == arr[right-1]) right--; left++; right--; } else if (sum < target) { left++; } else { right--; } } }
Memorization tip: All three templates share the same skeleton. The only things that change are: (1) where the pointers start, (2) what condition is evaluated, and (3) what happens when the condition is met. Master the skeleton, then fill in the blanks.
These are the classic LeetCode problems that use the two pointers pattern. Grouped by variant for focused practice.
Practice order: Start with #125 (Valid Palindrome) and #26 (Remove Duplicates) to build intuition for both variants. Then tackle #167 (Two Sum II) and #11 (Container With Most Water). Once comfortable, attempt #15 (3Sum) and #42 (Trapping Rain Water) for the full challenge.
Enter a sorted array and a target sum. Watch the opposite-direction two-pointer approach find a pair step by step.
In both variants, each pointer advances at most n times and never moves backward. The total number of steps across both pointers is bounded by 2n, which simplifies to O(n).
Two pointers work directly on the input array. You only need a constant number of variables (the pointer indices plus a few temporaries) regardless of input size. No auxiliary arrays, hash maps, or recursion stacks are needed.
Exception: 3Sum uses two pointers inside a loop over the array, making the total time O(n²). But the two-pointer portion is still O(n) per iteration. Sorting adds O(n log n), but the O(n²) from the outer loop dominates.