Two Pointer Bug: Unsorted Data Skips Valid Pairs
Unsorted input causes opposite-ends two-pointer to skip valid pairs silently — no error, wrong results.
20+ years shipping production code across the stack, with years spent interviewing engineers. Everything here is grounded in real deployments.
- Core concept: Two pointers eliminate O(n²) brute force by using sorted order or relative speeds to skip impossible pairs.
- Three variants exist: opposite-ends for sorted pair-finding, same-direction slow/fast for in-place modification, and fast-slow (Floyd's) for cycle detection.
- Each variant: O(n) time, O(1) space — no extra data structures.
- Performance insight: After sorting O(n log n), the pointer walk is O(n) — total O(n log n) beats O(n²) at input sizes above ~200.
- Production insight: Applying opposite-ends on unsorted data silently returns wrong results — always sort first or use a HashMap.
- Biggest mistake: Moving both pointers when only one should move — you'll skip valid pairs and produce false negatives.
Imagine you're looking for two puzzle pieces that fit together inside a sorted box. Instead of picking every possible pair and testing them one by one, you start with one person at each end of the box and they walk toward each other — one steps inward when the pair is too big, the other steps inward when it's too small. They meet in the middle having checked every useful combination without ever doubling back. That walk-toward-each-other trick is exactly what the two pointer technique is.
Every coding interview has a handful of patterns that show up again and again. Two pointers is one of the most common — and one of the most misunderstood. Candidates often brute-force their way through problems that a senior dev would knock out in O(n) time because they don't recognise the signal that two pointers is the right tool. Missing that signal doesn't just cost you efficiency points; it costs you the job.
The two pointer technique exists to eliminate the need for nested loops. Any time you find yourself writing a loop inside a loop to compare or relate two positions in the same collection, there's a good chance you're doing more work than you need to. Two pointers lets you move through the data from two positions simultaneously — either from opposite ends converging toward the middle, or from the same starting point racing at different speeds — and cut your time complexity from O(n²) down to O(n).
By the end of this article you'll be able to identify which of the three two-pointer variants to reach for (opposite ends, same direction, or fast-slow), implement the three most interview-common problems from scratch without hints, and explain your reasoning out loud — which is the part that actually gets you hired.
What Two Pointer Technique Actually Solves
The two-pointer technique uses two indices traversing a data structure to solve problems that would otherwise require nested loops. Instead of O(n²) brute force, you move pointers based on a condition — typically one pointer starts at each end, or both start at the beginning with one ahead. The core mechanic is that each pointer advances monotonically, never backtracking, which guarantees each element is visited at most once per pointer. This yields O(n) time and O(1) space for many array and string problems.
In practice, the technique works only when the data is sorted or when the problem has a monotonic property — like finding a pair that sums to a target in a sorted array. If the array is unsorted, moving pointers based on comparison operators (e.g., if sum < target, move left) is invalid because you cannot know whether a better pair lies ahead or behind. The pointer movement must be deterministic; any guess based on unsorted values will skip valid pairs.
Use two pointers when you need to find pairs, triplets, or subarrays that satisfy a condition in a sorted sequence, or when you need to reverse or partition in place. It is not a general-purpose optimization — it exploits order. In real systems, this pattern appears in merging sorted logs, detecting palindromes in user input, or finding the closest pair of points in a sorted list. Misapplying it to unsorted data is a common source of subtle bugs that pass unit tests but fail in production.
Why Two Pointers Beat Brute Force — The Core Intuition
Let's get the intuition rock solid before touching code. The brute-force approach to 'find two numbers that sum to a target' is to check every pair: index 0 with index 1, index 0 with index 2, and so on. That's n*(n-1)/2 comparisons — O(n²). It works, but it's slow and it signals to an interviewer that you haven't spotted the pattern.
The key insight is this: if your data is sorted, you already know something about every element's relationship to every other element without checking them all. If the number at the left pointer plus the number at the right pointer is too large, you don't need to check the right pointer against anything to its right — they'd all be even larger. So you move the right pointer one step inward. If the sum is too small, you move the left pointer one step inward. Each step eliminates an entire class of possibilities.
This is why two pointers almost always requires a sorted array for the opposite-ends variant. The sort itself costs O(n log n), but the pointer walk is O(n), so your overall complexity is O(n log n) — still far better than O(n²) for large inputs. When the input is already sorted, you get pure O(n).
Now here's a nuance most tutorials skip: the elimination trick only works because the array is monotonic. If you applied the same logic to an unsorted array, moving the left pointer when the sum is too small might actually skip the correct pair — because a larger value could be hiding to the left of the current left pointer. That's why sorting is non-negotiable for this variant.
Removing Duplicates In-Place — The Same-Direction Variant
Not every two-pointer problem has the pointers moving toward each other. The same-direction variant uses a slow pointer and a fast pointer both starting at the left — the fast pointer scouts ahead while the slow pointer marks where the next valid element should land.
This pattern is perfect for in-place array modification problems: removing duplicates, removing a specific value, or filtering elements without allocating a new array. The slow pointer essentially tracks 'the last confirmed valid position', and the fast pointer asks 'is this element worth keeping?'
Think of it like editing a manuscript. The fast-reading editor races through the draft, and every time they find a sentence worth keeping, they hand it to the slow typist who places it into the clean copy. The typist never skips ahead; the editor never looks back.
This approach runs in O(n) time and O(1) space — a big deal in interviews where the constraint 'do it in-place without extra memory' is explicitly stated. If you reach for a HashSet here, you've violated that constraint even though you'd get the right answer.
One subtlety: this variant requires that duplicates are adjacent, which is guaranteed when the array is sorted. For unsorted arrays, this slow/fast pattern won't remove all duplicates — only adjacent ones. If the input is unsorted, you either sort first (and the problem often allows it) or use a set.
Detecting a Cycle — The Fast-Slow (Floyd's) Variant
The third variant is the most elegant: the fast pointer moves two steps for every one step the slow pointer takes. If there's a cycle in a linked list, the fast pointer will eventually lap the slow pointer and they'll meet inside the cycle. If there's no cycle, the fast pointer will fall off the end of the list.
Why does this work? Think of two runners on a circular track — one running twice as fast as the other. They'll always meet eventually, no matter how long the track is. If the track is a straight road with a dead end, the faster runner hits the wall first and you know it's not circular.
This is Floyd's Cycle Detection algorithm. It's O(n) time and O(1) space — compare that to the HashSet approach, which is also O(n) time but O(n) space. In an interview, the follow-up 'can you do it without extra memory?' is almost guaranteed, and this is your answer.
This pattern extends beyond linked lists. It appears in problems about finding the duplicate number in an array, detecting infinite loops in graph traversal, and even in cryptography.
One common variation: after detecting the cycle, you can find the exact node where the cycle begins. Reset the slow pointer to head, then move both pointers one step at a time. The node where they meet is the start of the cycle. The proof involves the distance from head to cycle start being equal to the distance from the meeting point around the cycle back to the start.
fastRunner != null && fastRunner.next != null before advancing the fast pointer. Moving two steps means you access .next.next — if fastRunner.next is null, that second .next throws a NullPointerException. This is the single most common runtime error candidates hit on a whiteboard under pressure.Container With Most Water — Real Interview Walkthrough
This classic problem is a perfect test of opposite-ends two-pointer thinking. You're given an array of non-negative integers where each value represents the height of a vertical line at that index. You need to find two lines that together with the x-axis form a container that holds the most water.
The brute force checks every pair — O(n²). The two-pointer solution: start left at index 0 and right at the last index. The area is limited by the shorter line: area = min(height[left], height[right]) * (right - left). Move the pointer with the shorter line inward. Why? Because the only way to possibly get a larger area is to replace the limiting side. If you move the taller line, the width shrinks and the height can never exceed the shorter line — area can only stay the same or decrease.
This is the exact same elimination logic as two-sum: each move discards all pairs that would be worse than the current best. The algorithm runs in O(n) time and O(1) space. Here's the implementation in Java.
Three Sum Problem: Extending the Pattern to Multiple Pointers
The two-pointer pattern extends naturally to three-sum problems. The classic 'Given an array of integers, find all unique triplets that sum to zero' is a staple of coding interviews. The approach: sort the array, then fix one number at a time using a loop, and for the remaining range apply the two-sum two-pointer technique to find pairs that sum to -fixedNumber.
This gives O(n²) overall complexity — because the outer loop runs n times and the inner two-pointer walk is O(n). Without two pointers you'd be at O(n³).
The key challenge is handling duplicate values. After sorting, if you skip duplicate values for the fixed number, you avoid duplicate triplets. Similarly, inside the two-pointer walk, when you find a valid pair, move both pointers inward past any duplicate values before continuing.
This generalises to k-sum: fix k-2 numbers, then use two pointers on the remaining range. Complexity O(n^(k-1)).
String Palindromes: Two Pointers on Characters
Strings are arrays of characters, so two pointers work perfectly for palindrome checking. Start one pointer at index 0 and one at the last index. Compare characters, then move inward. If any pair doesn't match, it's not a palindrome.
This is O(n) time and O(1) space. A variant with character filtering (ignore non-alphanumeric, case-insensitive) is equally common — just advance pointers over invalid characters before comparing.
Two pointers also solve problems like 'reverse a string in-place' (swap characters from both ends) and 'valid palindrome II' (allow at most one deletion).
Easy Problems: Where You Prove You Can Walk Before You Run
Most interviewers don't start with Three Sum. They start with the easy variants to see if you understand pointer mechanics without the algorithmic overhead. These problems test whether you can spot when a single pass with two indices is enough — and they're embarrassingly common in phone screens.
Remove Duplicates from a Sorted Array? That's just a slow pointer tracking the write position and a fast pointer scanning for new values. Move Zeroes to End? Same pattern: slow pointer lands on the next non-zero slot, fast pointer finds the non-zero values. Two Sum on a sorted array? Classic opposite-direction pointers converging toward the target sum.
The trap here is overcomplicating. Juniors write hash maps for sorted arrays. They create temporary arrays when they should swap in-place. These problems exist to filter out candidates who reach for O(n²) brute force when O(n) is sitting right there.
Master these. They're the warm-up that separates engineers who "know of" two pointers from those who actually wield them.
Medium Problems: When You Need a Sorted Array and a Sharp Eye
Medium problems are where the interviewer stops holding your hand. The input isn't always sorted — but your success depends on making it so. Take 'Closest Pair Sum' or 'Count pairs with absolute difference equal to k'. The naive approach is O(n²) double loop. The two-pointer approach demands you sort first, then slide pointers.
Here's the pattern: sort the array, then initialize left at 0 and right at the end. Compute the current pair's sum. If it's too small, move left forward. If too large, move right backward. If you need a specific difference, adjust a single pointer until the gap matches. The sorting cost (O(n log n)) pays for itself by collapsing the inner loop.
The production reality? I've used this pattern to merge log files, reconcile financial transactions, and detect data drift between database snapshots. When you're comparing two sorted streams, two pointers are your cheapest option.
Don't just memorize the code. Understand why the sort is mandatory and why the pointers only move in one direction. That's what separates a solution from a guess.
Payment Reconciliation Silent Failure: Two-Pointer on Unsorted Data
- Always verify input sortedness before using opposite-ends two pointers.
- If the input is not sorted, either sort it first or use a different algorithm.
- Silent failures from algorithm assumptions are the hardest to debug — they produce wrong results, not crashes.
fastRunner != null && fastRunner.next != null before moving the fast pointer two steps.log.debug("left=%d, right=%d, sum=%d", left, right, sum);Check if array is sorted by iterating and comparing adjacent elements.Key takeaways
fastRunner != null && fastRunner.next != null) is not boilerplateCommon mistakes to avoid
4 patternsApplying opposite-ends two pointers to unsorted array
Not guarding the fast pointer's second step in cycle detection
fastRunner.next.next is called when fastRunner.next is null.while (fastRunner != null && fastRunner.next != null) before accessing fastRunner.next.next.Moving both pointers when only one should move (two-sum variant)
Forgetting to skip duplicate values in three-sum
Interview Questions on This Topic
Given an array of integers sorted in ascending order, write a function that finds all unique triplets that sum to zero. Walk me through your approach and complexity before you code.
java
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i-1]) continue;
int left = i+1, right = nums.length-1;
int target = -nums[i];
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (left < right && nums[left] == nums[left+1]) left++;
while (left < right && nums[right] == nums[right-1]) right--;
left++; right--;
} else if (sum < target) left++;
else right--;
}
}
return result;
}
``Frequently Asked Questions
20+ years shipping production code across the stack, with years spent interviewing engineers. Everything here is grounded in real deployments.
That's Coding Patterns. Mark it forged?
9 min read · try the examples if you haven't