Two Pointers
Published: May 7, 2026
Last updated: May 7, 2026
Two pointers is one of the highest ROI patterns for interview prep.
The main reason is not that the implementation is complicated. It usually is not. The value is in learning how to recognize when the problem is giving you permission to do less work.
If an array is sorted, or if a string can be scanned from both ends, or if you are really maintaining a shrinking window, there is a good chance a pointer-based approach is available.
This post starts with a walkthrough of one of the more common patterns you begin with online and then adds some other examples and visuals for other solutions.
Pair Sum - Sorted
Let us start with a classic prompt:
Given a sorted array of integers
numsand a targettarget, return the indices of the two numbers that add up to the target.
Example input:
const nums = [1, 2, 3, 4, 6, 8, 11]; const target = 10;
The expected answer is [1, 5] because nums[1] + nums[5] = 2 + 8 = 10.
Brute force intuition
The most direct thought is normally: try every pair.
export function pairSumSortedBruteForce(nums, target) { for (let i = 0; i < nums.length; i++) { for (let j = i + 1; j < nums.length; j++) { if (nums[i] + nums[j] === target) { return [i, j]; } } } return []; }
This is a perfectly fine place to begin in an interview because:
- It is correct.
- It gives you a baseline.
- It makes the inefficiency obvious.
The problem is the nested loops. In the worst case, you check almost every pair, which gives O(n^2) time.
Brute force
Target = 10
Every pair is checked until a match is found.
Sorted input
index 0
1
index 1
2
index 2
3
index 3
4
index 4
6
index 5
8
index 6
11
Step 1 of 11
Start with the first number
Set i = 0 and start the inner loop at j = 1.
State
left: -
right: -
current: -
compare: [0, 1]
sum/value: 3
answer: not yet
What the brute-force version misses is that the input is already sorted.
That one detail changes everything.
The two pointers idea
If the array is sorted, then the leftmost value is the smallest and the rightmost value is the largest.
That means each comparison gives us directional information:
- If the sum is too small, move
leftright to increase the sum. - If the sum is too large, move
rightleft to decrease the sum. - If the sum matches, we are done.
Instead of restarting work for every element, we keep squeezing the search space inward.
export function pairSumSorted(nums, target) { let left = 0; let right = nums.length - 1; while (left < right) { const sum = nums[left] + nums[right]; if (sum === target) { return [left, right]; } if (sum < target) { left += 1; } else { right -= 1; } } return []; }
This drops the runtime to O(n) and keeps space at O(1).
Two pointers on sorted input
Target = 10
Use the sorted order to decide which pointer to move.
Sorted input
index 0
1
index 1
2
index 2
3
index 3
4
index 4
6
index 5
8
index 6
11
Step 1 of 4
Start at both ends
1 + 11 = 12. Too large, so move the right pointer left.
State
left: 0
right: 6
current: -
compare: [0, 6]
sum/value: 12
answer: not yet
A useful interview sentence here is: "Because the array is sorted, each comparison lets us eliminate part of the search space."
Why the sorted property matters
Without sorting, moving a pointer left or right would not reliably tell us whether the sum should go up or down.
With sorting:
- Moving
leftright always gives a value that is the same or larger. - Moving
rightleft always gives a value that is the same or smaller.
That monotonic behavior is what makes the pointer movement safe.
Faster alternatives when the input is not already sorted
This post is centered on the sorted version of the problem, but in practice you will often be asked the unsorted variant too.
There are two common follow-up paths.
Sort first, then scan
If you want to keep the two-pointer strategy, you can sort first.
The catch is that if the problem asks for the original indices, you need to keep them around while sorting.
export function pairSumWithSort(nums, target) { const pairs = nums.map((value, index) => ({ value, index })); pairs.sort((a, b) => a.value - b.value); let left = 0; let right = pairs.length - 1; while (left < right) { const sum = pairs[left].value + pairs[right].value; if (sum === target) { return [pairs[left].index, pairs[right].index]; } if (sum < target) { left += 1; } else { right -= 1; } } return []; }
Complexity:
O(n log n)time because sorting dominates.O(n)space because we store value/index pairs.
Sort, then scan
Target = 10
Useful when the original input is not sorted but you still want the two-pointer idea.
Sorted value/index pairs
index 0
1 (orig 1)
index 1
2 (orig 5)
index 2
3 (orig 6)
index 3
4 (orig 4)
index 4
6 (orig 2)
index 5
8 (orig 0)
index 6
11 (orig 3)
Step 1 of 6
Decorate the input
Pair each number with its original index so you can sort without losing the answer positions.
State
left: -
right: -
current: -
compare: -
sum/value: -
answer: not yet
I like this version when the conversation is specifically about pointer patterns, because it shows you understand how to preserve the property you need.
Hash map lookup
If the array is unsorted and you want the fastest lookup-based solution, a hash map is usually the better fit.
export function pairSumHashMap(nums, target) { const seen = new Map(); for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; if (seen.has(complement)) { return [seen.get(complement), i]; } seen.set(nums[i], i); } return []; }
Complexity:
O(n)time.O(n)space.
Hash map lookup
Target = 10
Store seen values and look for each number's complement.
Unsorted input
index 0
8
index 1
1
index 2
6
index 3
11
index 4
4
index 5
2
index 6
3
Step 1 of 6
Visit index 0
Need 2 to complete 8, so store 8 -> 0 and continue.
State
left: -
right: -
current: 0
compare: -
sum/value: 8
answer: not yet
Seen map
The trade-off is clean:
- Two pointers needs sorted order but uses constant extra space.
- Hash map works on unsorted input but spends extra memory.
Identifying the pattern
When I read a problem like this, I mentally check for these clues.
1. Is the input sorted?
This is the loudest hint.
If the data is sorted and the task involves pairs, ranges, or comparing extremes, two pointers should immediately be on the shortlist.
2. Am I asked about a pair, triplet, or contiguous range?
Problems involving:
- two numbers,
- three numbers,
- a window over a string or array,
often become pointer problems once ordering is useful.
3. Does each comparison eliminate possibilities?
This is the real question.
If one comparison tells you "everything to the left is now too small" or "everything to the right is now too large", the problem is probably pointer-friendly.
4. Am I repeatedly rescanning work?
If your brute-force version uses nested loops, ask whether a pointer can carry forward information from the last comparison instead of starting over.
Tips and tricks
Say the invariant out loud
For this problem, the invariant is that the answer, if it exists, must stay inside the current [left, right] range.
When the sum is too small, moving right would only make things smaller or equal, so the only useful move is left += 1.
Here, the "invariant" means a condition or fact that stays true throughout the algorithm while the loop is running.
Ask whether the array is already sorted
If the prompt does not say so, ask.
That one clarification can change the best solution from hash map to two pointers.
Watch the output requirements
If the question wants original indices and you decide to sort, preserve the indices before sorting.
Distinguish pattern from implementation
The pattern is not just "have two variables called left and right".
The pattern is using structure in the input so pointer movement is meaningful.
Two-pointer traversals
There are three two-pointer strategies to know about:
- Inward traversal.
- Unidirectional traversal.
- Staged traversal.
Inward traversal
The two pointers start at opposing ends and work towards each other.
Inward traversal
Start from both ends and shrink toward the middle.
index 0
r
index 1
a
index 2
c
index 3
e
index 4
c
index 5
a
index 6
r
Step 1 of 7
Initialize both pointers
Place left at the first character and right at the last character.
State
fixed: -
left: 0
right: 6
stage: single pass
Common examples:
Valid PalindromeTwo Sum II - Input Array Is SortedContainer With Most Water
Unidirectional traversal
Both pointers start at the same end of the data structure.
Unidirectional traversal
Both pointers move left-to-right, but they do different jobs.
index 0
1
index 1
1
index 2
2
index 3
3
index 4
3
Step 1 of 7
Reader and writer start near the front
The slow pointer marks where the next unique value should go. The fast pointer scans ahead.
State
fixed: -
left: 0
right: 1
stage: single pass
Common examples:
Is SubsequenceRemove Duplicates From Sorted ArrayMove Zeroes
Staged traversal
One pointer establishes context first, and then another pointer pair or follow-up pointer traversal takes over within that smaller search space.
Staged traversal
One pointer establishes context first, then other pointers take over inside that context.
index 0
-4
index 1
-1
index 2
-1
index 3
0
index 4
1
index 5
2
Step 1 of 6
Stage 1: fix one value
Choose a pivot index first. In 3Sum, this pointer sets the first number in the triplet.
State
fixed: 1
left: -
right: -
stage: fixed pivot
Common examples:
3Sum4SumTrapping Rain Waterwhen you reason in phases around boundary pointers
Similar problems worth practicing
These names are often used as standard interview-problem titles, but the useful part is the pattern hiding underneath each one.
Valid Palindrome: compare characters from the outside in
You place one pointer at the start of the string and one at the end.
If the characters match, move both inward. If they do not, the string is not a palindrome. This is one of the clearest examples of two pointers on a string.
Is Subsequence: scan one string while trying to consume the other
This is another useful string variant, but the pointers do different jobs.
- One pointer walks through
s, the smaller string we want to match. - The other pointer walks through
t, the larger string we are scanning. - When the characters match, advance both pointers.
- When they do not match, only advance the pointer in
t.
If the pointer in s reaches the end, then every character in s appeared in order inside t.
function isSubsequence(s: string, t: string): boolean { let i = 0, j = 0; while (i < s.length && j < t.length) { if (s[i] === t[j]) { i++; } j++; } return i === s.length; }
Is Subsequence
Two pointers across two strings
The pointer in `s` only moves on a match. The pointer in `t` keeps scanning forward.
Current case
s: abc
t: ahbgdc
s (the subsequence we want to match)
index 0
a
index 1
b
index 2
c
t (the string we scan through)
index 0
a
index 1
h
index 2
b
index 3
g
index 4
d
index 5
c
Step 1 of 8
Start at the first characters
Compare s[0] = a with t[0] = a.
State
i in s: 0
j in t: 0
matched chars: 0
result: in progress
Two Sum II: find a pair in a sorted array
This is basically the same shape as the pair sum problem we just walked through.
The array is sorted, so each sum tells you whether to move the left pointer right or the right pointer left.
The main twist is that the answer must be returned as 1-indexed positions.
One small correction to the implementation you pasted: right should start at numbers.length - 1, not numbers.length, otherwise the first lookup is out of bounds.
function twoSum(numbers: number[], target: number): number[] { let left = 0; let right = numbers.length - 1; while (left < right) { const sum = numbers[left] + numbers[right]; if (sum === target) { return [left + 1, right + 1]; } if (sum < target) { left++; } else { right--; } } return []; }
Quick guidance:
leftstarts at the smallest value.rightstarts at the largest value.- If the sum is too small, move
leftright. - If the sum is too large, move
rightleft. - Return
left + 1andright + 1because the problem is 1-indexed.
Two Sum II
Sorted array with 1-indexed output
Same two-pointer idea as pair sum, but return positions starting from 1 instead of 0.
numbers | target = 9
index 1
2
index 2
7
index 3
11
index 4
15
Step 1 of 5
Start from both ends
Initialize left at index 0 and right at index 3.
State
left: 0 (returns 1)
right: 3 (returns 4)
sum: 17
answer: not yet
3Sum: reduce a triplet problem into repeated pair searches
You sort the array, fix one number, and then use two pointers on the remaining suffix to look for the other two values.
This is a good example of how one pointer pattern can become a building block inside a larger solution.
function threeSum(nums: number[]): number[][] { nums.sort((a, b) => a - b); const results: number[][] = []; const n = nums.length; for (let i = 0; i < n - 2; i++) { if (i > 0 && nums[i] === nums[i - 1]) continue; let left = i + 1; let right = n - 1; while (left < right) { const sum = nums[i] + nums[left] + nums[right]; if (sum === 0) { results.push([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 < 0) { left++; } else { right--; } } } return results; }
Notes on the solution:
- Sorting is what makes the inner
leftandrightmovement meaningful. - The outer loop fixes one value, then the inner loop solves a pair-style search for the remaining two values.
- You must skip duplicate fixed values, or you will produce duplicate triplets.
- After finding a valid triplet, you also skip duplicate
leftandrightvalues before continuing. - The runtime is
O(n^2)because for each fixed index, the inner pointers scan the remaining suffix once.
3Sum
Fixed index + inner two-pointer scan
This is a staged traversal: one outer pointer fixes a value, then the remaining suffix is searched with left and right pointers.
Sorted input
index 0
-4
index 1
-1
index 2
-1
index 3
0
index 4
1
index 5
2
Step 1 of 14
Sort first
3Sum becomes much easier once the array is sorted because the inner search can use the same left/right reasoning as pair sum.
Results so far
i: 0
left: 1
right: 5
sum: -3
triplets: none yet
Container With Most Water: move the side that limits the answer
You start with pointers at both ends and compute the area formed by the two heights.
The key move is to advance the shorter wall, because the shorter wall is currently limiting the area.
Remove Duplicates From Sorted Array: use a slow writer and a fast reader
This is still a two-pointers problem, but the pointers do different jobs.
One pointer scans forward through the array while the other marks where the next unique value should be written.
Squares of a Sorted Array: compare the extremes and fill from the back
When negatives are involved, the largest square may come from either end of the sorted array.
So you compare both ends, place the larger square into the result from right to left, and move the pointer that produced it.
A quick decision guide
If I had to compress the choice into a few lines:
- Already sorted and need a pair: start with two pointers.
- Unsorted and need the fastest lookup: use a hash map.
- Unsorted but want to leverage ordering: sort and then run two pointers.
What comes next
I will keep extending this post with more two-pointers examples.
Next up will likely be problems such as valid palindrome, where the same idea shows up in strings instead of arrays.
The main thing I want to reinforce is this: do not memorize the pointer movement rules in isolation. Train yourself to ask what property of the input makes pointer movement safe.
That is the part that transfers across problems.
Two Pointers
Introduction