Two pointers is one of the most common patterns in coding interviews. You keep two integer indices moving along an array or string — usually called left/right or i/j — and make O(1) work per step. Done right, the whole algorithm stays linear.
This guide follows the same format as a structured algorithms course: we introduce the idea, then walk through classic examples with interactive animations. The walkthrough problems are for learning — you don't need to solve them cold on first read.
What Is Two Pointers?
Two pointers means using two index variables that move along an iterable (array or string). They never jump randomly — each step advances at least one pointer in a controlled way, which is why runtime stays O(n) (or O(n + m) for two inputs).
There are two main families:
| Pattern | Setup | When to use |
|---|---|---|
| Opposite ends | left = 0, right = n − 1, move inward | Palindrome, two sum on sorted array, container with most water |
| Parallel scan | i and j on one or two arrays, move forward | Merge sorted arrays, is subsequence, intersection of sorted lists |
Pattern 1 — Opposite Ends (Move Inward)
Start one pointer at the first index and one at the last. Loop until they meet:
function fn(arr):
left = 0
right = arr.length - 1
while left < right:
# problem-specific logic
# then: left++, or right--, or bothWhy it's O(n): the pointers start n apart and move closer every iteration — at most O(n) loop iterations. If inner work is O(1), total time is O(n) and space is O(1).
Example 1 — Valid Palindrome
A string is a palindrome if it reads the same forward and backward ("racecar", "abcdcba").
After reversing, the first character must equal the last, the second must equal the second-last, and so on. Two pointers check pairs from both ends:
Valid Palindrome
Check if "racecar" reads the same forward and backward
Input
Compare s[0] vs s[6]
'r' === 'r' ✓ — move inward
function isPalindrome(s) {
let left = 0;
let right = s.length - 1;
while (left < right) {
if (s[left] !== s[right]) return false;
left++;
right--;
}
return true;
}
isPalindrome("racecar"); // true
isPalindrome("abcde"); // falseComplexity: O(n) time, O(1) space — only two integers no matter how long the string.
Example 2 — Two Sum on a Sorted Array
Given a sorted array of unique integers and a target, return whether any pair sums to target. (LeetCode 167. Two Sum II.)
Brute force checks every pair: O(n²). Because the array is sorted, opposite-end pointers do better:
- If
nums[left] + nums[right] > target→ sum too big →right-- - If sum
< target→ sum too small →left++ - If sum
=== target→ found
Two Sum II (Sorted Input)
nums = [1,2,4,6,8,9,14,15], target = 13
Input
Sum = 1 + 15 = 16
16 > 13 → decrease sum, move right--
Why moving right is safe when sum is too large: nums[left] is the smallest value we can pair with nums[right]. If even that sum exceeds target, no smaller partner for nums[right] exists — so right can never be part of a solution. Symmetric logic applies when the sum is too small.
function twoSumSorted(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) return true;
if (sum > target) right--;
else left++;
}
return false;
}
twoSumSorted([1, 2, 4, 6, 8, 9, 14, 15], 13); // true (4 + 9)Complexity: O(n) time, O(1) space.
Pattern 2 — Parallel Scan (Two Inputs)
When you have two sorted arrays (or a pattern + text), start i and j at index 0 and advance one or both:
function fn(arr1, arr2):
i = j = 0
while i < arr1.length AND j < arr2.length:
# compare arr1[i] vs arr2[j]
# advance i, j, or both
# exhaust leftovers if neededAt most n + m pointer moves → O(n + m) when inner work is O(1).
Example 3 — Merge Two Sorted Arrays
Combine two sorted arrays into one sorted array.
Naive approach: concatenate + sort → O((n+m) log(n+m)). Two pointers merge in O(n + m):
Merge Two Sorted Arrays
Build [1,2,3,4,5,6] from [1,3,5] and [2,4,6]
arr1
arr2
Compare 1 vs 2
Take 1 → ans = [1]
function mergeSorted(arr1, arr2) {
const ans = [];
let i = 0;
let j = 0;
while (i < arr1.length && j < arr2.length) {
if (arr1[i] <= arr2[j]) ans.push(arr1[i++]);
else ans.push(arr2[j++]);
}
while (i < arr1.length) ans.push(arr1[i++]);
while (j < arr2.length) ans.push(arr2[j++]);
return ans;
}
mergeSorted([1, 3, 5], [2, 4, 6]); // [1,2,3,4,5,6]Example 4 — Is Subsequence
Given strings s and t, return whether s is a subsequence of t (LeetCode 392).
"ace" is a subsequence of "abcde" — letters appear in order with gaps allowed. "aec" is not (wrong order).
Walk t with j. When s[i] === t[j], you "found" the next character of s → i++. Always j++. Success when i === s.length.
Is Subsequence
Is s = "ace" a subsequence of t = "abcde"?
s
t
s[0] == t[0] ('a')
Match → i++
function isSubsequence(s, t) {
let i = 0;
let j = 0;
while (i < s.length && j < t.length) {
if (s[i] === t[j]) i++;
j++;
}
return i === s.length;
}
isSubsequence("ace", "abcde"); // true
isSubsequence("aec", "abcde"); // falseComplexity: O(|s| + |t|) time, O(1) space.
Two Pointers vs Sliding Window
Both use two indices, but the intent differs:
| Two pointers | Sliding window | |
|---|---|---|
| Goal | Compare or merge from two positions | Maintain a contiguous subarray/substring |
| Pointer movement | Often opposite ends or two separate arrays | Usually both move forward (L, R) |
| Classic problems | Palindrome, two sum II, merge, subsequence | Max sum size k, longest unique substring |
Many problems blur the line — that's fine. The label matters less than recognizing linear movement with O(1) per step.
When the Template Shifts
The patterns above are guidelines, not laws:
- Sometimes both pointers start at index 0 and move forward on the same array (e.g. remove duplicates in-place).
- Sometimes you need three pointers (3Sum extends two sum with a fixed outer index).
- Sorted input is often required for the opposite-ends trick; unsorted two sum needs a hash map instead.
Stay flexible — if the iterable structure suggests pairing ends or scanning two sequences in parallel, try two pointers first.
Complexity Cheat Sheet
| Problem | Time | Space |
|---|---|---|
| Valid palindrome | O(n) | O(1) |
| Two sum II (sorted) | O(n) | O(1) |
| Merge two sorted arrays | O(n + m) | O(1)* |
| Is subsequence | O(n + m) | O(1) |
* Output array not counted toward extra space.
Practice Problems (Easiest → Hardest)
Easiest → Hardest
Step through the animations in this post first, then click any problem to open it on LeetCode.
Quick Reference — Pattern Picker
Pattern Picker
Click a node to trace the path — cards below update with your selection
Key Takeaways
- Two indices, linear moves. Each pointer advances in a controlled way — that's the O(n) guarantee.
- Opposite ends when comparing pairs from both sides or the input is sorted.
- Parallel scan when merging or matching across two sequences.
- Decide which pointer moves before you code — write the rule in plain English first.
- Sorted input unlocks opposite ends for two sum; unsorted two sum needs a hash map.
- 3Sum = sort + outer index + two pointers — a natural extension of pattern 1.
- Step through the animations. If you can narrate each pointer move, you can implement it.
If this helped, pair it with the Sliding Window Technique guide for contiguous subarray problems.
Share this post