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 sum | Move |
|---|---|
| Too small | Increase left |
| Too large | Decrease right |
| Equal | Found 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.
| Pattern | Typical goal |
|---|---|
| Two pointers | Search, compare, merge, partition, or detect structure |
| Sliding window | Maintain 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 type | Typical complexity |
|---|---|
| Pair search in sorted array | O(n) |
| Merge two sorted arrays | O(n + m) |
| In-place compaction | O(n) |
| Linked list middle or cycle check | O(n) |
| Sort first, then two pointers | O(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.
Related reading
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.