Two Pointers Pattern
1. Opposite Ends Technique (left and right pointers)
| Pattern | Initialization | Movement Rule | Use Case |
|---|---|---|---|
| Two Sum Sorted | left = 0, right = n-1 |
Move left if sum too small, right if too large | Find pair with target sum in sorted array |
| Palindrome Check | left = 0, right = n-1 |
Move both inward while characters match | Verify if string is palindrome |
| Container Most Water | left = 0, right = n-1 |
Move pointer with smaller height | Maximize area between two vertical lines |
| Reverse Array | left = 0, right = n-1 |
Swap and move both pointers inward | In-place array reversal |
Example: Two sum in sorted array
function twoSum(numbers, target) {
let left = 0;
let right = numbers.length - 1;
while (left < right) {
const sum = numbers[left] + numbers[right];
if (sum === target) {
return [left, right]; // or [left+1, right+1] for 1-indexed
} else if (sum < target) {
left++; // Need larger sum
} else {
right--; // Need smaller sum
}
}
return [-1, -1]; // Not found
}
// Example
console.log(twoSum([2, 7, 11, 15], 9)); // Output: [0, 1]
Example: Valid palindrome
function isPalindrome(s) {
// Normalize: remove non-alphanumeric, lowercase
s = s.toLowerCase().replace(/[^a-z0-9]/g, '');
let left = 0;
let right = s.length - 1;
while (left < right) {
if (s[left] !== s[right]) {
return false;
}
left++;
right--;
}
return true;
}
// Example
console.log(isPalindrome("A man, a plan, a canal: Panama")); // Output: true
2. Fast and Slow Pointers (Floyd's Cycle Detection)
| Pattern | Speed Ratio | Detection | Application |
|---|---|---|---|
| Cycle Detection | Slow: +1, Fast: +2 | Fast meets slow inside cycle | Detect loop in linked list (Floyd's algorithm) |
| Middle Element | Slow: +1, Fast: +2 | When fast reaches end, slow at middle | Find middle node of linked list |
| Cycle Start | Reset one pointer to head after meeting | Both move at same speed, meet at cycle start | Find beginning of cycle in linked list |
| Happy Number | Fast computes twice per iteration | Converge to 1 or detect cycle | Determine if number is happy number |
Example: Detect cycle in linked list
function hasCycle(head) {
if (!head || !head.next) return false;
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next; // Move 1 step
fast = fast.next.next; // Move 2 steps
if (slow === fast) {
return true; // Cycle detected
}
}
return false; // No cycle
}
Example: Find cycle start position
function detectCycle(head) {
let slow = head, fast = head;
// Phase 1: Detect if cycle exists
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
// Phase 2: Find cycle start
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return slow; // Cycle start
}
}
return null; // No cycle
}
3. In-place Array Manipulation O(1) Space
| Technique | Pointer Strategy | Space | Common Problem |
|---|---|---|---|
| Remove Duplicates | Slow tracks unique position, fast scans | O(1) |
Remove duplicates from sorted array |
| Move Zeros | Slow marks non-zero position, fast scans | O(1) |
Move all zeros to end of array |
| Remove Element | Write pointer for valid elements | O(1) |
Remove all instances of target value |
| Squares Sorted Array | Compare from both ends, fill from back | O(n) |
Square sorted array with negatives |
Example: Remove duplicates from sorted array
function removeDuplicates(nums) {
if (nums.length === 0) return 0;
let slow = 0; // Position for next unique
for (let fast = 1; fast < nums.length; fast++) {
if (nums[fast] !== nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1; // New length
}
// [1,1,2,2,3] -> [1,2,3,_,_], return 3
Example: Move zeros to end
function moveZeroes(nums) {
let slow = 0; // Next non-zero position
// Move all non-zeros forward
for (let fast = 0; fast < nums.length; fast++) {
if (nums[fast] !== 0) {
nums[slow] = nums[fast];
slow++;
}
}
// Fill remaining with zeros
while (slow < nums.length) {
nums[slow] = 0;
slow++;
}
}
Example: Sorted squares with negative numbers
function sortedSquares(nums) {
const n = nums.length;
const result = new Array(n);
let left = 0, right = n - 1;
let pos = n - 1; // Fill from back
while (left <= right) {
const leftSquare = nums[left] * nums[left];
const rightSquare = nums[right] * nums[right];
if (leftSquare > rightSquare) {
result[pos] = leftSquare;
left++;
} else {
result[pos] = rightSquare;
right--;
}
pos--;
}
return result;
}
// [-4,-1,0,3,10] -> [0,1,9,16,100]
4. Partitioning and Sorting Techniques
| Algorithm | Invariant | Partitions | Use Case |
|---|---|---|---|
| Dutch National Flag | [0..low-1]=0, [high+1..n-1]=2 |
Three partitions: 0s, 1s, 2s | Sort array with 3 distinct values |
| QuickSort Partition | Elements < pivot left, > pivot right | Two partitions around pivot | Partition step in quicksort |
| Partition Array | Reorder by condition | Elements meeting condition first | Separate even/odd, positive/negative |
| Kth Element | QuickSelect partition | Eliminate half each iteration | Find kth largest/smallest element |
Example: Dutch National Flag (Sort colors)
function sortColors(nums) {
let low = 0; // Boundary for 0s
let mid = 0; // Current element
let high = nums.length - 1; // Boundary for 2s
while (mid <= high) {
if (nums[mid] === 0) {
[nums[low], nums[mid]] = [nums[mid], nums[low]];
low++;
mid++;
} else if (nums[mid] === 1) {
mid++;
} else { // nums[mid] === 2
[nums[mid], nums[high]] = [nums[high], nums[mid]];
high--;
// Don't increment mid - need to check swapped element
}
}
}
// [2,0,2,1,1,0] -> [0,0,1,1,2,2]
Example: Partition array around pivot
function partition(arr, low, high) {
const pivot = arr[high];
let i = low - 1; // Index of smaller element
for (let j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
// Place pivot in correct position
[arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
return i + 1; // Pivot index
}
5. Collision Detection Patterns
| Pattern | Scenario | Detection Method | Application |
|---|---|---|---|
| Meeting Point | Pointers converge from opposite ends | Check if pointers cross or meet | Binary search, palindrome validation |
| Circular Collision | Fast pointer catches slow in cycle | Fast laps slow pointer | Linked list cycle detection |
| Gap Closure | Distance between pointers decreases | Monitor pointer distance | Container with most water |
| Boundary Check | Pointers reach array bounds | Index validation before access | Prevent out-of-bounds errors |
Example: Remove nth node from end of list
function removeNthFromEnd(head, n) {
const dummy = { next: head };
let fast = dummy;
let slow = dummy;
// Move fast n+1 steps ahead
for (let i = 0; i <= n; i++) {
fast = fast.next;
}
// Move both until fast reaches end
while (fast) {
fast = fast.next;
slow = slow.next;
}
// Remove nth node
slow.next = slow.next.next;
return dummy.next;
}
6. Space Optimization O(1) Complexity
| Technique | Space Saved | Trade-off | Example |
|---|---|---|---|
| In-place Modification | O(n) → O(1) |
May modify input array | Remove duplicates, move zeros |
| Pointer Instead of Copy | Avoid array cloning | Need to track positions carefully | Reverse string, partition array |
| Overwrite Strategy | Reuse input array for output | Original data lost | Merge sorted arrays in-place |
| Cycle Detection Memory | O(n) → O(1) |
Only works for cyclic structures | Floyd's algorithm vs HashSet |
Example: Reverse string in-place
function reverseString(s) {
let left = 0;
let right = s.length - 1;
while (left < right) {
// Swap characters
[s[left], s[right]] = [s[right], s[left]];
left++;
right--;
}
// O(1) space - no extra array needed
}
// ['h','e','l','l','o'] -> ['o','l','l','e','h']
Note: Two pointers technique achieves O(1) space complexity for many problems that would otherwise require O(n) space. The key is recognizing when pointers can replace additional data structures like HashSets or extra arrays.
Two Pointers Pattern Summary
- Opposite Ends: Start from both ends, move based on condition (sorted arrays)
- Fast-Slow: Different speeds reveal patterns (cycle detection, middle element)
- Same Direction: One tracks position, other scans (remove duplicates)
- Partitioning: Maintain invariants between pointer regions (Dutch flag)
- Key Benefit: O(n) time with O(1) space - optimal for in-place operations
Warning: Always validate pointer positions before dereferencing. In fast-slow patterns, check both
fast and fast.next to prevent null pointer errors. For opposite-ends technique, ensure left < right condition is correct.