Two pointers scan from opposite ends or move in the same direction to solve problems in O(n) that would otherwise require O(nยฒ) โ the core technique for sorted-array pair problems, partitioning, and linked-list cycle detection.
Algorithms
Two Pointers Technique
Two indices walk through a structure โ from opposite ends, same direction, or on separate sequences โ reducing O(nยฒ) brute force to O(n). The backbone of sorted-array pair problems, in-place partitioning, and linked-list cycle detection.
Two pointers is a technique where two index variables traverse a data structure simultaneously โ either moving toward each other from opposite ends, or moving in the same direction at different speeds. The key insight: by choosing which pointer to advance based on a comparison, you avoid examining every pair (O(nยฒ)) and instead visit each element at most once or twice (O(n)). The technique requires some form of order or structure โ typically a sorted array, a partitioned condition, or a linked list where relative position matters. Two pointers appears in four common patterns: (1) opposite-end convergence on a sorted array (Two Sum II, Container With Most Water), (2) same-direction read/write for in-place partitioning (Remove Duplicates, Move Zeroes), (3) fast/slow runners on linked lists (cycle detection, middle node), and (4) merging two sorted sequences. Each pattern is a different configuration of the same core idea: two indices, one movement rule, O(n) total.
Analogy: Two people searching a bookshelf from opposite ends. Person L starts at the left and steps right; person R starts at the right and steps left. They compare what they're holding: if the combined weight is too light, L steps right (to a heavier book); if too heavy, R steps left (to a lighter book). They meet in the middle having checked every possible lightest+heaviest pairing โ without ever checking middle+middle or restarting from the beginning.
02
Section Two ยท Mental Model
How It Thinks
Array is sorted โ increasing from left to right
โถ
Moving the left pointer right INCREASES the sum of the two elements; moving the right pointer left DECREASES it โ enabling directed search toward a target sum
Each step advances at least one pointer โ either L++ or R-- (or both)
โถ
At most n steps total before L and R meet โ every element visited at most once โ O(n) guaranteed
The "read" pointer scans ahead while the "write" pointer marks where to place the next valid element
โถ
In-place partitioning: the read pointer finds valid elements, the write pointer compacts them โ no extra array needed, O(1) space
Fast pointer moves 2 steps per iteration, slow pointer moves 1 step
โถ
If a cycle exists, fast catches slow inside the cycle โ like two runners on a circular track where the faster one always laps the slower one
Two sorted sequences each have a pointer starting at their beginning
โถ
Compare the two pointed elements, take the smaller, advance that pointer โ merges two sorted sequences in O(n+m)
Two pointers doesn't require sorted data for all patterns โ partitioning and cycle detection work on unsorted data
โถ
The common thread is not "sorted" but "each pointer moves in one direction only" โ monotonic pointer movement guarantees termination and O(n) time
03
Section Three ยท Opposite Ends
Pattern 1 โ Opposite-End Convergence
Start left pointer at index 0 and right pointer at index n-1. Compare, then advance the pointer that improves the result. The pointers converge toward each other, meeting in the middle. This only works when the array is sorted (or the problem has a similar monotonic property).
Two Sum II โ Sorted Array (LC 167)
Find two numbers in a sorted array that sum to target. Return their 1-indexed positions.
Pseudocode
functiontwoSum(arr, target):
L = 0, R = arr.length - 1while L < R:
sum = arr[L] + arr[R]
if sum == target:
return [L + 1, R + 1]
else if sum < target:
L = L + 1// sum too small โ need biggerelse:
R = R - 1// sum too big โ need smallerreturn [-1, -1] // O(n)
Two Sum II โ target = 10 on [1, 3, 5, 7, 9]
Container With Most Water (LC 11)
Given heights, find two lines that form a container holding the most water. Width = R - L, height = min(arr[L], arr[R]).
Pseudocode
functionmaxArea(heights):
L = 0, R = heights.length - 1
maxWater = 0while L < R:
width = R - L
h = min(heights[L], heights[R])
maxWater = max(maxWater, width ร h)
if heights[L] < heights[R]:
L = L + 1// shorter side limits area โ move itelse:
R = R - 1return maxWater // O(n)
Why move the shorter side?:
The area is limited by the shorter line.
Moving the taller line inward can only decrease width without increasing height โ guaranteed worse.
Moving the shorter line inward decreases width but might find a taller line โ the only chance to improve.
04
Section Four ยท Same Direction
Pattern 2 โ Read/Write (Same Direction)
Both pointers start at the beginning and move right. The "read" pointer (fast) scans every element. The "write" pointer (slow) marks the position where the next valid element should be placed. This pattern compacts an array in-place โ removing duplicates, zeroes, or specific values without extra space.
Remove Duplicates from Sorted Array (LC 26)
Remove duplicates in-place from a sorted array. Return the count of unique elements.
Pseudocode
functionremoveDuplicates(arr):
if arr.length == 0: return0
write = 1// first element always uniquefor read = 1to arr.length - 1:
if arr[read] != arr[read - 1]: // new unique element
arr[write] = arr[read]
write = write + 1return write // O(n), O(1) space
The read/write pattern works for any filter condition:
Remove duplicates (arr[read] != arr[write-1]), move zeroes (arr[read] != 0), remove element (arr[read] != val), remove even numbers โ the structure is identical.
Only the condition changes.
05
Section Five ยท Fast & Slow
Pattern 3 โ Fast & Slow Pointers (Floyd's)
Fast pointer moves 2 steps per iteration, slow pointer moves 1 step. If there's a cycle, they meet inside it. If there's no cycle, fast reaches the end. This is Floyd's cycle detection algorithm โ O(n) time, O(1) space. Beyond cycle detection, the same technique finds the middle of a linked list (when fast reaches the end, slow is at the middle) and detects cycles in abstract sequences (like "Happy Number" LC 202).
Pseudocode โ Linked List Cycle Detection
functionhasCycle(head):
slow = head, fast = head
while fast != nulland fast.next != null:
slow = slow.next // 1 step
fast = fast.next.next // 2 stepsif slow == fast:
return true// met inside cyclereturn false// fast reached end โ no cycle// O(n), O(1) space
Floyd's Cycle Detection โ fast catches slow inside the cycle
Find Middle Node โ Fast/Slow
functionfindMiddle(head):
slow = head, fast = head
while fast != nulland fast.next != null:
slow = slow.next
fast = fast.next.next
return slow // slow is at the middle
Floyd's algorithm also finds the cycle START:
After slow and fast meet inside the cycle, reset one pointer to head.
Move both at speed 1 โ they meet again at the cycle's entry point.
This solves LC 142 "Linked List Cycle II" and is provable mathematically: if the tail has length a and the cycle has length c, they meet at distance a from the cycle start.
06
Section Six ยท Merge Pattern
Pattern 4 โ Merge Two Sequences
Two pointers, each starting at the beginning of a different sorted sequence. Compare the two pointed elements, process the smaller one, advance that pointer. Continues until both sequences are exhausted. This is the merge step of Merge Sort, and the basis of "Merge Sorted Array" (LC 88), "Merge Two Sorted Lists" (LC 21), and "Intersection of Two Arrays" (LC 349).
Pseudocode โ Merge Two Sorted Arrays
functionmerge(A, B):
i = 0, j = 0
result = []
while i < A.length and j < B.length:
if A[i] <= B[j]:
result.add(A[i++])
else:
result.add(B[j++])
// copy remaining elements from A or Bwhile i < A.length: result.add(A[i++])
while j < B.length: result.add(B[j++])
return result // O(n + m)
Merge in-place (LC 88 trick):
When merging into array A which has enough space, fill from the END (right to left) โ compare the largest unplaced elements and put them at the back.
Build each pattern once โ they're short enough to memorise. In interviews, the code is 5โ10 lines; the hard part is recognising which pattern to apply.
Java โ All four patterns
// 1. Opposite ends โ Two Sum II โ O(n)int[] twoSum(int[] arr, int target) {
int L = 0, R = arr.length - 1;
while (L < R) {
int sum = arr[L] + arr[R];
if (sum == target) return newint[]{L, R};
else if (sum < target) L++;
else R--;
}
return newint[]{-1, -1};
}
// 2. Read/write โ Remove Duplicates โ O(n), O(1)intremoveDuplicates(int[] arr) {
if (arr.length == 0) return 0;
int w = 1;
for (int r = 1; r < arr.length; r++)
if (arr[r] != arr[r - 1]) arr[w++] = arr[r];
return w;
}
// 3. Fast/slow โ Cycle detection โ O(n), O(1)booleanhasCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
Two Pointers โ Full Implementation
Java โ All two-pointer patterns
import java.util.*;
public classTwoPointers {
// โโโ 1. Two Sum II (opposite ends) โโโโโโโ O(n)staticint[] twoSum(int[] arr, int target) {
int L = 0, R = arr.length - 1;
while (L < R) {
int s = arr[L] + arr[R];
if (s == target) return newint[]{L + 1, R + 1};
else if (s < target) L++;
else R--;
}
return newint[]{-1, -1};
}
// โโโ 2. 3Sum (sort + opposite ends) โโโโโโ O(nยฒ)staticList<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = newArrayList<>();
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // skip dupsint L = i + 1, R = nums.length - 1;
while (L < R) {
int s = nums[i] + nums[L] + nums[R];
if (s == 0) {
res.add(Arrays.asList(nums[i], nums[L], nums[R]));
while (L < R && nums[L] == nums[L + 1]) L++;
while (L < R && nums[R] == nums[R - 1]) R--;
L++; R--;
} else if (s < 0) L++;
else R--;
}
}
return res;
}
// โโโ 3. Container With Most Water โโโโโโโโ O(n)staticintmaxArea(int[] h) {
int L = 0, R = h.length - 1, max = 0;
while (L < R) {
max = Math.max(max, (R - L) * Math.min(h[L], h[R]));
if (h[L] < h[R]) L++; else R--;
}
return max;
}
// โโโ 4. Remove Duplicates (read/write) โโโ O(n)staticintremoveDuplicates(int[] arr) {
if (arr.length == 0) return 0;
int w = 1;
for (int r = 1; r < arr.length; r++)
if (arr[r] != arr[r - 1]) arr[w++] = arr[r];
return w;
}
// โโโ 5. Move Zeroes (read/write) โโโโโโโโโ O(n)static voidmoveZeroes(int[] arr) {
int w = 0;
for (int r = 0; r < arr.length; r++)
if (arr[r] != 0) arr[w++] = arr[r];
while (w < arr.length) arr[w++] = 0;
}
// โโโ 6. Valid Palindrome (opposite ends) โ O(n)staticbooleanisPalindrome(String s) {
int L = 0, R = s.length() - 1;
while (L < R) {
while (L < R && !Character.isLetterOrDigit(s.charAt(L))) L++;
while (L < R && !Character.isLetterOrDigit(s.charAt(R))) R--;
if (Character.toLowerCase(s.charAt(L)) != Character.toLowerCase(s.charAt(R)))
return false;
L++; R--;
}
return true;
}
// โโโ 7. Sort Colors โ Dutch National Flag โ O(n)static voidsortColors(int[] arr) {
int lo = 0, mid = 0, hi = arr.length - 1;
while (mid <= hi) {
if (arr[mid] == 0) { swap(arr, lo++, mid++); }
else if (arr[mid] == 1) { mid++; }
else { swap(arr, mid, hi--); }
}
}
static voidswap(int[] a, int i, int j) {
int t = a[i]; a[i] = a[j]; a[j] = t;
}
// โโโ Main โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโpublic static voidmain(String[] args) {
System.out.println(Arrays.toString(twoSum(newint[]{2,7,11,15}, 9)));
System.out.println(threeSum(newint[]{-1,0,1,2,-1,-4}));
System.out.println(maxArea(newint[]{1,8,6,2,5,4,8,3,7}));
System.out.println(isPalindrome("A man, a plan, a canal: Panama"));
}
}
Python โ Two-pointer implementations
# โโโ 1. Two Sum II (opposite ends) โโโโโโโ O(n)deftwo_sum(arr, target):
L, R = 0, len(arr) - 1
while L < R:
s = arr[L] + arr[R]
if s == target: return [L + 1, R + 1]
elif s < target: L += 1
else: R -= 1
return [-1, -1]
# โโโ 2. 3Sum โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ O(nยฒ)defthree_sum(nums):
nums.sort()
res = []
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i-1]: continue
L, R = i + 1, len(nums) - 1
while L < R:
s = nums[i] + nums[L] + nums[R]
if s == 0:
res.append([nums[i], nums[L], nums[R]])
while L < R and nums[L] == nums[L+1]: L += 1
while L < R and nums[R] == nums[R-1]: R -= 1
L += 1; R -= 1
elif s < 0: L += 1
else: R -= 1
return res
# โโโ 3. Remove Duplicates (read/write) โโโโ O(n)defremove_duplicates(arr):
if not arr: return 0
w = 1
for r in range(1, len(arr)):
if arr[r] != arr[r-1]:
arr[w] = arr[r]; w += 1
return w
# โโโ 4. Move Zeroes โโโโโโโโโโโโโโโโโโโโโโโ O(n)defmove_zeroes(arr):
w = 0
for r in range(len(arr)):
if arr[r] != 0:
arr[w] = arr[r]; w += 1
while w < len(arr):
arr[w] = 0; w += 1
# โโโ 5. Cycle Detection (fast/slow) โโโโโโโ O(n)defhas_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast: return Truereturn False# โโโ 6. Valid Palindrome โโโโโโโโโโโโโโโโโโ O(n)defis_palindrome(s):
L, R = 0, len(s) - 1
while L < R:
while L < R and not s[L].isalnum(): L += 1
while L < R and not s[R].isalnum(): R -= 1
if s[L].lower() != s[R].lower(): return False
L += 1; R -= 1
return True# Demo
print(two_sum([2,7,11,15], 9)) # [1, 2]
print(three_sum([-1,0,1,2,-1,-4])) # [[-1,-1,2],[-1,0,1]]
print(is_palindrome("A man, a plan, a canal: Panama")) # True
09
Section Nine ยท Java Reference
Use It In Java
IN JAVA โ Two-Pointer Idioms
Array indexint L = 0, R = arr.length - 1; โ most common form
ListNodeListNode slow = head, fast = head; โ linked list traversal
Pattern
Loop condition
Key idiom
Opposite ends
while (L < R)
L++ or R-- based on comparison
Read/write
for (r = 0; r < n; r++)
arr[w++] = arr[r] when condition met
Fast/slow
while (fast != null && fast.next != null)
slow = slow.next; fast = fast.next.next
Merge
while (i < m && j < n)
Take smaller, advance that pointer
3Sum
outer for + inner while (L < R)
Skip duplicates with while (nums[L]==nums[L+1]) L++
โ Gotcha: In the fast/slow linked list pattern, always check fast != null && fast.next != null BEFORE accessing fast.next.next โ otherwise you get a NullPointerException on odd-length lists. The order of the null check matters: fast first, then fast.next.
โ Gotcha: In 3Sum, skipping duplicates requires checking AFTER adding a result โ while (L < R && nums[L] == nums[L+1]) L++. Skipping before checking misses valid triplets; skipping after ensures you find at least one instance of each unique triplet.
10
Section Ten ยท Practice
Problems To Solve
Two-pointer problems test pattern recognition more than coding skill โ every problem is 5โ15 lines once you identify the right pattern. The challenge is knowing whether to use opposite ends (sorted pair), read/write (in-place compact), or fast/slow (linked list). Read the problem constraints: sorted array + pair โ opposite ends. In-place + O(1) space โ read/write. Linked list + cycle or middle โ fast/slow.
Fast/slow to detect cycle. Then reset one to head, advance both at speed 1 โ they meet at the cycle entry point. Mathematical proof: tail length = remaining cycle distance.
Interview Pattern:
Two pointers is the most concise technique โ every solution is under 15 lines.
The interview skill is not writing the code but recognising the pattern. Sorted + pair โ opposite ends.
In-place filter โ read/write. Linked list structure โ fast/slow. Once you identify the pattern, the code writes itself.