When you see a problem that talks about pairs, sub‑arrays, or moving windows, the two‑pointer pattern is often the cleanest solution. The idea is simple: keep two indices (or node references) that move through the data structure at different speeds or in opposite directions. By adjusting them based on the condition you’re checking, you can explore all relevant combinations in a single linear pass.
Why Two Pointers Work
- Linear scan – Instead of nested loops (O(n²)), you move the pointers together, achieving O(n).
- Constant extra memory – You only store a few indices or node references, so space stays O(1).
- Monotonic progress – Because the data is usually sorted or the condition is monotonic, moving a pointer forward never invalidates previous work.
Signals That a Problem Calls for Two Pointers
| Cue in the statement | Typical goal | Reason it fits the pattern |
|---|---|---|
| "Find a pair that sums to X" | Pair search | Sorted array lets you shrink/expand the window. |
| "Shortest subarray that contains …" | Minimum window | Expanding right pointer, contracting left. |
| "Remove duplicates in‑place" | In‑place modification | One pointer reads, the other writes. |
| "Detect a cycle / find middle" | Linked list traversal | Fast pointer moves twice as fast as slow. |
| "Rotate array k steps" | Reversal technique | Three reversals use start‑end pointers. |
If you notice any of these phrases, pause and ask yourself whether you can maintain two moving markers that together capture the condition.
Worked Example: "Two Sum II – Input Array Is Sorted"
Problem: Given a sorted list of integers nums and a target t, return the indices of the two numbers that add up to t. Assume exactly one solution exists.
Approach: Place one pointer at the start (l) and one at the end (r). Compute s = nums[l] + nums[r]. If s equals t, you are done. If s is too small, move l right to increase the sum. If s is too large, move r left to decrease the sum. Repeat until the pair is found.
def two_sum_sorted(nums, target):
l, r = 0, len(nums) - 1
while l < r:
cur = nums[l] + nums[r]
if cur == target:
return l, r
if cur < target:
l += 1
else:
r -= 1
raise ValueError("No valid pair found")
Complexity: The loop moves each pointer at most n steps, so time is O(n). Only two integer variables are used, giving O(1) extra space.
Common pitfalls:
- Forgetting to handle 0‑based vs 1‑based indexing when the platform expects 1‑based results.
- Not checking for overflow when dealing with very large integers (in languages without big‑int support).
- Assuming the array is sorted when the problem statement only says "non‑decreasing" – duplicates can affect the pointer movement logic.
Five Representative Practice Problems
1. Remove Duplicates from Sorted Array
Goal: Overwrite the array so each unique element appears once, and return the new length.
Hint: Use a write pointer that lags behind a read pointer. When nums[read] != nums[write], advance write and copy the value.
2. Longest Substring Without Repeating Characters
Goal: Find the length of the longest window that contains no duplicate characters. Hint: Treat the left and right bounds as pointers. Expand the right pointer, and when a repeat appears, shrink the left pointer until the window is valid again. A hash map stores the last index of each character.
3. Container With Most Water
Goal: Given heights of vertical lines, pick two lines that together with the x‑axis form a container of maximal area. Hint: Start with pointers at both ends. Compute area, then move the pointer that points to the shorter line inward, because only a taller line could improve the area.
4. Minimum Window Substring
Goal: Find the smallest substring of s that contains all characters of t.
Hint: Expand the right pointer until the window includes all required characters, then contract the left pointer to shrink the window while still satisfying the requirement. Track counts with a dictionary.
5. Find the Middle of a Linked List
Goal: Return the node at the midpoint of a singly linked list.
Hint: Advance one pointer (slow) one step at a time and another (fast) two steps. When fast reaches the end, slow is at the middle.
Common Mistakes and How to Avoid Them
- Assuming monotonicity – Not all sorted‑array problems are monotonic. Verify that moving a pointer in one direction cannot make a previously false condition true again.
- Off‑by‑one errors – When the loop condition is
while l < r, remember thatl == ris the termination point; never accessnums[l]after the loop. - Ignoring edge cases – Empty inputs, single‑element arrays, or strings with all identical characters often break naïve pointer logic.
- Mixing pointer updates – Updating both pointers in the same iteration can skip viable combinations. Decide which pointer to move based on the current comparison.
How to Practice This
- Write the skeleton – For each problem, start by declaring two indices (
l,r) and awhileloop that checksl < r(or the appropriate condition). - Simulate on paper – Walk through a small example step‑by‑step, updating pointers manually to see the invariant you are maintaining.
- Use Call Assistant – Record yourself explaining the approach aloud, then replay it to catch filler words or unclear logic. The assistant can keep the discussion focused on the pattern, helping you internalize the narrative.
FAQ
When should I prefer two pointers over a hash‑map? Use two pointers when the input is sorted or when you need to preserve order with O(1) extra space. Hash‑maps give O(n) time but O(n) space, which is unnecessary for many monotonic problems.
Can two pointers be used on unsorted data? Yes, but you typically need to sort first, which adds O(n log n) time. If the problem constraints allow sorting, the overall complexity may still be acceptable.
What if the array contains negative numbers? The two‑pointer technique still works as long as the array is sorted. The comparison logic (
<or>) remains the same.How do I adapt the pattern for linked lists? Replace array indices with node references. Move pointers by following
nextpointers, and be careful withNonechecks to avoid dereferencing null.
Frequently asked questions
When should I prefer two pointers over a hash‑map?
Use two pointers when the input is sorted or when you need O(1) extra space. Hash‑maps give O(n) time but require O(n) additional memory, which is unnecessary for many monotonic problems.
Can two pointers be used on unsorted data?
Yes, but you usually sort first, adding O(n log n) time. If the problem allows sorting within the time limits, the overall complexity may still be acceptable.
What if the array contains negative numbers?
The technique still works as long as the array is sorted. The comparison logic (`<` or `>`) does not change because the ordering is preserved.
How do I adapt the pattern for linked lists?
Replace array indices with node references and move pointers by following `next`. Ensure you check for `None` before accessing a node to avoid runtime errors.
#coding pattern#two pointers#algorithm#interview prep#python