Two Pointers uses two indices moving through data together to turn brute force O(n²) into O(n).
Its core idea:
Instead of checking all pairs, move intelligently based on a condition.
Focus on recognizing:
Sorted data + pair/search problem → Two Pointers
Core Template
public int twoPointers(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
if (conditionMet(arr[left], arr[right])) {
// process answer
}
if (shouldMoveLeft()) {
left++;
} else {
right--;
}
}
return result;
}def two_pointers(arr):
left, right = 0, len(arr) - 1
while left < right:
if condition_met(arr[left], arr[right]):
pass # process answer
if should_move_left():
left += 1
else:
right -= 1
return resultint twoPointers(vector<int>& arr) {
int left = 0;
int right = (int)arr.size() - 1;
while (left < right) {
if (conditionMet(arr[left], arr[right])) {
// process answer
}
if (shouldMoveLeft()) left++;
else right--;
}
return result;
}function twoPointers(arr) {
let left = 0,
right = arr.length - 1;
while (left < right) {
if (conditionMet(arr[left], arr[right])) {
// process answer
}
if (shouldMoveLeft()) left++;
else right--;
}
return result;
}Movement is the algorithm: decide → move one pointer → never backtrack.
Pattern 1: Opposite Ends (Pair Sum in Sorted Array)
Watch opposite-end pointers converge on [2,7,11,15] with target 9 — each comparison eliminates one candidate. Press ▶ to animate.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Two Pointers (Opposite Ends & Same Direction)
Two pointers replace nested loops. Opposite-ends pointers solve sorted-pair problems in O(n) by eliminating a whole end each step; same-direction pointers dedupe or partition in place with a write/read split.
Sorted [2,7,11,15], target 9. 2+15=17 > 9 → 15 can never pair with anything (it's the biggest) → drop it. 2+11=13 > 9 → drop 11. 2+7=9 ✓ found. Watch how each probe kills one entire end; 3 probes instead of 6 pairs.
1
left = 0, right = n - 1
2
while left < right:
3
sum = arr[left] + arr[right]
4
if sum == target: return pair
5
if sum < target: left++ // need bigger
6
else: right-- // need smaller
7
return none
1
write = 0 // next unique slot
2
for read in 1..n-1:
3
if arr[read] != arr[write]:
4
write++
5
arr[write] = arr[read] // pull it left
6
return write + 1 // count of uniques
public boolean twoSumSorted(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) return true;
if (sum < target) {
left++;
} else {
right--;
}
}
return false;
}def two_sum_sorted(arr, target):
left, right = 0, len(arr) - 1
while left < right:
s = arr[left] + arr[right]
if s == target:
return True
if s < target:
left += 1
else:
right -= 1
return Falsebool twoSumSorted(vector<int>& arr, int target) {
int left = 0, right = (int)arr.size() - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) return true;
if (sum < target) left++;
else right--;
}
return false;
}function twoSumSorted(arr, target) {
let left = 0,
right = arr.length - 1;
while (left < right) {
const sum = arr[left] + arr[right];
if (sum === target) return true;
if (sum < target) left++;
else right--;
}
return false;
}Sorted order lets each comparison eliminate a whole row or column of the pair matrix.
Pattern 2: Fast & Slow (Remove Duplicates)
slow owns the unique prefix; fast explores. A new value gets copied down.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Remove Duplicates (Fast & Slow Pointers)
Deduplicate a sorted array in place. A slow pointer guards the end of the unique prefix; a fast pointer scans ahead and, only when it finds a new value, copies it past the slow pointer. O(n) time, O(1) extra space.
Array [1,1,2,2,3]. slow guards the unique zone [1]; fast scans. Duplicates (1, then 2) are skipped; new values (2, then 3) make slow++ and get written down. Watch the array change as 2 and 3 are pulled left: [1,2,3,2,3]. The first slow+1 slots (3) are the answer; the tail is leftover garbage and that's fine.
1
slow = 0
2
for fast in 1..n-1:
3
if nums[fast] != nums[slow]:
4
slow++
5
nums[slow] = nums[fast] // extend unique prefix
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int slow = 0;
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}def remove_duplicates(nums):
if not nums:
return 0
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1int removeDuplicates(vector<int>& nums) {
if (nums.empty()) return 0;
int slow = 0;
for (int fast = 1; fast < (int)nums.size(); fast++) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}function removeDuplicates(nums) {
if (nums.length === 0) return 0;
let slow = 0;
for (let fast = 1; fast < nums.length; fast++) {
if (nums[fast] !== nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}Fast reads, slow writes — one pass filters the array in place.
Pattern 3: Partitioning (Dutch National Flag)
Three pointers sort 0s, 1s and 2s in a single pass — watch each swap lock a region.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Dutch National Flag (One Pass vs Counting)
Sort an array containing only 0, 1, and 2. The elegant one-pass solution is Dutch National Flag (three pointers); the easy alternative is counting sort (tally then overwrite in two passes).
Array [2,0,2,1,1,0]. low=mid=0, high=5. mid sees 2 → swap with high (2↔0) and high--, but DO NOT advance mid (the swapped-in value is unexamined). 0 → swap into the 0-zone and advance both. 1 → just mid++. Watch the three zones meet: [0,0,1,1,1,2] in one pass. The key trap is rechecking mid after a 2-swap.
1
low = 0, mid = 0, high = n - 1
2
while mid <= high:
3
if nums[mid] == 0:
4
swap(low, mid); low++; mid++ // 0 → front
5
elif nums[mid] == 1:
6
mid++ // 1 stays middle
7
else:
8
swap(mid, high); high-- // 2 → back; recheck mid!
1
# pass 1: tally
2
c0 = c1 = c2 = 0
3
for x in nums: c[x]++
4
5
# pass 2: overwrite
6
write c0 zeros, then c1 ones, then c2 twos
Three pointers (low, mid, high) classify elements into regions — covered fully in Partition.
Same-direction pointers that maintain invariant regions, not pairs.
Pattern 4: Merge Two Sorted Arrays
Compare the heads, take the smaller, advance that pointer — linear merge.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Merge Two Sorted Arrays
Merge two sorted arrays into one sorted output using two pointers (i on A, j on B) that always compare the two current heads and take the smaller. One pass, O(n+m). This is the inner loop of merge sort.
A=[1,3,5], B=[2,4,6] laid out side by side. Compare heads: take 1 (A), then 2 (B), 3 (A), 4 (B), then B is exhausted so A's 5 (and 6) drop in. The 'out' state chip shows the merged prefix growing: [1,2,3,4,5,6]. The a[i]/b[j] pointers mark the next candidate from each side.
1
i = j = k = 0
2
while both arrays have items:
3
if a[i] <= b[j]: out[k++] = a[i++]
4
else: out[k++] = b[j++]
5
copy leftovers
public int[] mergeSorted(int[] a, int[] b) {
int i = 0, j = 0, k = 0;
int[] res = new int[a.length + b.length];
while (i < a.length && j < b.length) {
if (a[i] < b[j]) {
res[k++] = a[i++];
} else {
res[k++] = b[j++];
}
}
while (i < a.length) res[k++] = a[i++];
while (j < b.length) res[k++] = b[j++];
return res;
}def merge_sorted(a, b):
i = j = 0
res = []
while i < len(a) and j < len(b):
if a[i] < b[j]:
res.append(a[i])
i += 1
else:
res.append(b[j])
j += 1
res.extend(a[i:])
res.extend(b[j:])
return resvector<int> mergeSorted(vector<int>& a, vector<int>& b) {
vector<int> res;
int i = 0, j = 0;
while (i < (int)a.size() && j < (int)b.size())
res.push_back(a[i] < b[j] ? a[i++] : b[j++]);
while (i < (int)a.size()) res.push_back(a[i++]);
while (j < (int)b.size()) res.push_back(b[j++]);
return res;
}function mergeSorted(a, b) {
const res = [];
let i = 0,
j = 0;
while (i < a.length && j < b.length)
res.push(a[i] < b[j] ? a[i++] : b[j++]);
while (i < a.length) res.push(a[i++]);
while (j < b.length) res.push(b[j++]);
return res;
}Two independent pointers, each owning one sorted input.
Pattern 5: Palindrome Check
Mirrored pairs compare and vanish; crossing in the middle certifies a palindrome.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Valid Palindrome (Opposite Ends)
Check a string is a palindrome by comparing the two ends and walking inward. The first mismatch returns false; if the pointers cross, every mirrored pair matched.
String 'racecar'. L and R start at the ends: r=r ✓, a=a ✓, c=c ✓. They cross at the middle 'e', which never needs a partner → palindrome. A single mismatch (e.g. 'racecat') would return false at that one comparison. O(n) time, O(1) space.
1
left = 0, right = n-1
2
while left < right:
3
if s[left] != s[right]: return false
4
left++, right--
5
return true
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
if (Character.toLowerCase(s.charAt(left)) !=
Character.toLowerCase(s.charAt(right))) {
return false;
}
left++;
right--;
}
return true;
}def is_palindrome(s):
left, right = 0, len(s) - 1
while left < right:
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return Truebool isPalindrome(string s) {
int left = 0, right = (int)s.size() - 1;
while (left < right) {
while (left < right && !isalnum(s[left])) left++;
while (left < right && !isalnum(s[right])) right--;
if (tolower(s[left]) != tolower(s[right])) return false;
left++;
right--;
}
return true;
}function isPalindrome(s) {
const t = s.toLowerCase();
const alnum = (c) => /[a-z0-9]/.test(c);
let left = 0,
right = t.length - 1;
while (left < right) {
while (left < right && !alnum(t[left])) left++;
while (left < right && !alnum(t[right])) right--;
if (t[left] !== t[right]) return false;
left++;
right--;
}
return true;
}Shrink from both ends, skipping anything irrelevant along the way.
Common Mistakes
Using opposite ends on unsorted data.
The “move which pointer?” decision depends on ordering. Sort first or use hashing instead.
Moving both pointers blindly.
Exactly one pointer moves per step, chosen by the condition — otherwise you skip answers.
Wrong loop bound.
left < right for pairs; left <= right when the middle element matters.
Complexity
| Pattern | Time |
|---|---|
| Opposite ends | O(n) |
| Fast & slow | O(n) |
| Merge | O(n + m) |
Premium Content
Unlock Two Pointers and all premium lessons with a subscription.
From ₹199.99/year — See plans