Introduction
After learning Binary Search, you saw how narrowing the search space dramatically improves performance. The Two Pointer technique applies a similar idea — but instead of halving the search space, we use two pointers that move through the data from different positions, eliminating unnecessary comparisons.
This technique can transform a brute-force $O(n^2)$ solution (with nested loops) into a clean $O(n)$ solution.
Two Main Patterns
Pattern 1: Opposite-Direction Pointers
Two pointers start at opposite ends of a sorted array and move toward each other.
[1, 3, 5, 7, 11, 15]
^ ^
left right
Use for: Finding pairs that satisfy a condition (e.g., two numbers that add up to a target).
Pattern 2: Same-Direction Pointers (Fast & Slow)
Two pointers start at the same end but move at different speeds.
[0, 0, 1, 1, 1, 2, 2, 3]
^
slow
^
fast
Use for: Removing duplicates, partitioning arrays, detecting cycles in linked lists.
Problem 1: Two Sum on a Sorted Array
Given: A sorted array and a target sum. Find: Two numbers that add up to the target.
Brute-Force Approach — $O(n^2)$
// Check every pair — nested loops
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] + arr[j] == target) return new int[]{i, j};
}
}
Two Pointer Approach — $O(n)$
Since the array is sorted, we can do much better:
- Place
leftat index 0 andrightat the last index. - Calculate
sum = arr[left] + arr[right]. - If
sum == target, we found the answer. - If
sum < target, moveleftforward (we need a bigger number). - If
sum > target, moverightbackward (we need a smaller number).
Walkthrough
Find two numbers in [2, 7, 11, 15, 20] that add up to 22.
| Step | left | right | arr[left] | arr[right] | Sum | Action |
|---|---|---|---|---|---|---|
| 1 | 0 | 4 | 2 | 20 | 22 | ✅ Found! |
That was quick. Let’s try target 26.
| Step | left | right | arr[left] | arr[right] | Sum | Action |
|---|---|---|---|---|---|---|
| 1 | 0 | 4 | 2 | 20 | 22 | 22 < 26 → move left |
| 2 | 1 | 4 | 7 | 20 | 27 | 27 > 26 → move right |
| 3 | 1 | 3 | 7 | 15 | 22 | 22 < 26 → move left |
| 4 | 2 | 3 | 11 | 15 | 26 | ✅ Found! |
Java Implementation
public class TwoSumSorted {
/**
* Finds two indices in a sorted array whose values sum to the target.
* Uses opposite-direction two pointers.
*
* @return array of two indices, or empty array if no solution exists
*/
public static int[] twoSum(int[] sortedArr, int target) {
int left = 0;
int right = sortedArr.length - 1;
while (left < right) {
int sum = sortedArr[left] + sortedArr[right];
if (sum == target) {
return new int[]{left, right};
} else if (sum < target) {
left++; // we need a bigger sum → move left pointer forward
} else {
right--; // we need a smaller sum → move right pointer backward
}
}
return new int[]{}; // no pair found
}
public static void main(String[] args) {
int[] arr = {2, 7, 11, 15, 20};
int[] result = twoSum(arr, 26);
System.out.println(result[0] + ", " + result[1]); // Output: 2, 3
}
}
Why does this work? Because the array is sorted:
- If the sum is too small, increasing
leftcan only increase the sum. - If the sum is too large, decreasing
rightcan only decrease the sum. - We never skip a valid pair because we exhaust all useful combinations.
Problem 2: Remove Duplicates from a Sorted Array (In-Place)
Given: A sorted array with duplicates. Goal: Remove duplicates in-place and return the count of unique elements.
This uses the same-direction pattern.
Walkthrough
Remove duplicates from [1, 1, 2, 2, 2, 3, 4, 4].
slow marks the position for the next unique element. fast scans ahead.
| Step | fast | arr[fast] | arr[slow]? | Action | Array State |
|---|---|---|---|---|---|
| 1 | 1 | 1 | Same as slow (1) | Skip | [1, 1, 2, 2, 2, 3, 4, 4] |
| 2 | 2 | 2 | Different! | slow++, copy | [1, 2, 2, 2, 2, 3, 4, 4] |
| 3 | 3 | 2 | Same as slow (2) | Skip | |
| 4 | 4 | 2 | Same | Skip | |
| 5 | 5 | 3 | Different! | slow++, copy | [1, 2, 3, 2, 2, 3, 4, 4] |
| 6 | 6 | 4 | Different! | slow++, copy | [1, 2, 3, 4, 2, 3, 4, 4] |
| 7 | 7 | 4 | Same as slow (4) | Skip |
Unique elements: 4 (indices 0–3 contain [1, 2, 3, 4]). ✅
Java Implementation
public class RemoveDuplicates {
/**
* Removes duplicates from a sorted array in-place.
* Returns the count of unique elements.
*
* Uses same-direction two pointers:
* slow = boundary of the unique portion
* fast = scanner looking for new unique values
*/
public static int removeDuplicates(int[] sortedArr) {
if (sortedArr.length == 0) return 0;
int slow = 0; // last index of the unique portion
for (int fast = 1; fast < sortedArr.length; fast++) {
if (sortedArr[fast] != sortedArr[slow]) {
slow++;
sortedArr[slow] = sortedArr[fast]; // place the new unique value
}
// if equal, just skip — fast moves forward automatically
}
return slow + 1; // count of unique elements
}
public static void main(String[] args) {
int[] arr = {1, 1, 2, 2, 2, 3, 4, 4};
int uniqueCount = removeDuplicates(arr);
System.out.println("Unique count: " + uniqueCount); // Output: 4
// arr[0..3] = [1, 2, 3, 4]
}
}
Problem 3: Check If a String Is a Palindrome
Given: A string. Goal: Determine if it reads the same forwards and backwards.
This is another opposite-direction pattern.
Java Implementation
public class PalindromeCheck {
/**
* Checks if a string is a palindrome (ignoring case).
* Uses opposite-direction two pointers.
*/
public static boolean isPalindrome(String s) {
int left = 0;
int right = s.length() - 1;
while (left < right) {
if (Character.toLowerCase(s.charAt(left)) !=
Character.toLowerCase(s.charAt(right))) {
return false; // mismatch found
}
left++;
right--;
}
return true; // all characters matched
}
public static void main(String[] args) {
System.out.println(isPalindrome("RaceCar")); // true
System.out.println(isPalindrome("Hello")); // false
}
}
Time & Space Complexity
| Problem | Time | Space | Why |
|---|---|---|---|
| Two Sum (sorted) | $O(n)$ | $O(1)$ | Each pointer moves at most $n$ times total. |
| Remove Duplicates | $O(n)$ | $O(1)$ | Single pass with fast. |
| Palindrome Check | $O(n)$ | $O(1)$ | Pointers meet in the middle. |
The beauty of Two Pointer is that all these problems would need $O(n^2)$ with brute-force nested loops, but with two pointers they drop to $O(n)$.
When to Use / When NOT to Use
| ✅ Use When | ❌ Avoid When |
|---|---|
| Array is sorted (or can be sorted) | Data is unsorted and sorting would change the answer (e.g., index-based problems) |
| Looking for pairs or sub-arrays that satisfy a condition | The problem requires checking all possible subsets (not just pairs) |
| You want to optimise from $O(n^2)$ to $O(n)$ | A hash-based approach would be simpler and sufficient |
Practice Problems
- Two Sum II – LeetCode #167 — Opposite-direction pointers on a sorted array.
- Remove Duplicates from Sorted Array – LeetCode #26 — Same-direction pointers.
- Valid Palindrome – LeetCode #125 — Opposite-direction with character filtering.
- Container With Most Water – LeetCode #11 — A clever two-pointer greedy problem.
- 3Sum – LeetCode #15 — Combine sorting with two pointers inside a loop.
What’s Next?
The Two Pointer technique works with pairs and endpoints. But what about problems involving contiguous sub-arrays? That is where the Sliding Window Technique comes in — a powerful extension of the same-direction pattern.