Two Pointers

Easy-Medium
Time: O(n)Space: O(1)
0/16
problems solved
0%
1.

What are Two Pointers?

Two Pointers is a technique that uses two indices to traverse data, eliminating the need for nested loops.

The Race Track Analogy: Imagine two runners on a track:

  • Opposite direction: Start at opposite ends, run toward each other
  • Same direction: Start together, one runs faster than the other

Visual Representation:

OPPOSITE DIRECTION: SAME DIRECTION:
←─────────────→ ─────────────→
L R S F
[1, 2, 3, 4, 5] [1, 2, 2, 3, 3]
Move toward center Fast scans, Slow marks

Why Two Pointers?

  • Reduces O(n²) brute force to O(n)
  • Uses O(1) extra space
  • Works naturally with sorted arrays
  • Elegant solutions to complex problems

The Core Insight: Instead of checking ALL pairs (n² pairs), use pointers strategically to SKIP unnecessary comparisons based on problem constraints.

2.

Why Two Pointers Matter in Interviews

Two Pointers is one of the most common patterns in coding interviews.

Frequency:

  • Appears in 20-30% of FAANG coding rounds
  • Foundation for many other patterns (Sliding Window, Fast/Slow)
  • Often combined with sorting

What Interviewers Test:

  1. Optimization Thinking - Can you improve brute force?
  2. Sorted Array Skills - Do you leverage sorted order?
  3. In-Place Modifications - Can you avoid extra space?
  4. Edge Case Handling - Duplicates, empty arrays, boundaries

The Two Main Variants:

VariantMovementUse Case
Opposite DirectionL→ ←RFinding pairs, palindrome
Same DirectionS→ F→In-place modification

Key Insight: Sorting often ENABLES two pointers. If brute force is O(n²), consider if O(n log n) sort + O(n) two pointers is better.

3.

Opposite Direction: The Fundamental Pattern

Two pointers starting at opposite ends, moving toward center.

When to Use:

  • Finding pairs with target sum in SORTED array
  • Palindrome checking
  • Container/area problems
  • Reversing in-place

The Template:

left = 0, right = n - 1
while left < right:
if condition_met:
return result
elif need_bigger:
left++
else:
right--

Why It Works on Sorted Arrays:

Array: [1, 3, 5, 7, 9], Target = 10
If arr[left] + arr[right] > target:
- arr[left] + anything_to_right_of_right > target too!
- So move right-- (eliminate ALL pairs with current right)
If arr[left] + arr[right] < target:
- arr[right] + anything_to_left_of_left < target too!
- So move left++ (eliminate ALL pairs with current left)

Each move eliminates multiple pairs at once → O(n)!

4.

Two Sum II (Sorted Array)

The classic opposite-direction problem. Find two numbers that sum to target.

Visual Trace: arr = [2, 7, 11, 15], target = 9

Step 1: left=0, right=3
2 + 15 = 17 > 9
Sum too big → move right--
[2, 7, 11, 15]
L R
Step 2: left=0, right=2
2 + 11 = 13 > 9
Sum too big → move right--
[2, 7, 11, 15]
L R
Step 3: left=0, right=1
2 + 7 = 9 ✓
Found! Return [0, 1]
[2, 7, 11, 15]
L R

Proof of Correctness: We never skip a valid pair because:

  • Moving left++ only when sum < target (keeping right won't help)
  • Moving right-- only when sum > target (keeping left won't help)
Code
Java
5.

Container With Most Water

Find two vertical lines that form a container holding the most water.

Key Formula: Area = min(height[left], height[right]) × width

The Greedy Insight: Start with the WIDEST container (left=0, right=n-1). As we shrink width, we need TALLER lines to compensate. So always move the SHORTER line inward.

Why Move the Shorter Line?

The shorter line LIMITS the height.
Keeping the shorter line while shrinking width can ONLY decrease area.
The taller line might pair with something even taller inside.
Example: heights = [1, 8, 6, 2, 5, 4, 8, 3, 7]
Initial: L=0 (h=1), R=8 (h=7)
Area = min(1,7) × 8 = 8
1 is shorter → move left++
L=1 (h=8), R=8 (h=7)
Area = min(8,7) × 7 = 49 ← Maximum!
7 is shorter → move right--
... continue ...
Code
Java
6.

Trapping Rain Water

Calculate how much water can be trapped after raining. A harder variant!

Key Insight: Water at position i = min(maxLeft, maxRight) - height[i]

Two Pointer Approach: Track maxLeft and maxRight as we go. Process the side with smaller max.

Why Process Smaller Max Side?

If maxLeft < maxRight:
- Water at left is bounded by maxLeft (guaranteed)
- Even if maxRight could be higher on right, maxLeft limits us
- Safe to calculate and move left
Example: [0,1,0,2,1,0,1,3,2,1,2,1]
Visualization:
#
# # # #
# # # # # # # #
0 1 0 2 1 0 1 3 2 1 2 1
Water fills to: min(left_max, right_max) at each position
Total trapped = 6 units
Code
Java
7.

Same Direction: Fast/Slow Pointers

Both pointers move in the same direction at different speeds or conditions.

When to Use:

  • Removing elements in-place
  • Removing duplicates
  • Partitioning arrays
  • Finding cycle (linked lists)

The Template:

slow = 0 // Position for valid elements
for fast = 0 to n-1:
if arr[fast] meets condition:
arr[slow] = arr[fast]
slow++
return slow // New length

Visual Concept:

[2, 2, 3, 3, 4] Remove duplicates
S F fast scans
[2, _, _, _, _]
S F slow marks where to write
[2, 3, _, _, _]
S F
[2, 3, 4, _, _]
S Result length = 3

Key Insight: Slow marks the "frontier" of valid elements. Fast explores ahead.

8.

Remove Duplicates from Sorted Array

Remove duplicates IN-PLACE from sorted array. Return new length.

The Logic:

  • slow: position of last unique element
  • fast: scans for next unique element
  • When fast finds new value, copy to slow+1

Visual Trace: [1, 1, 2, 2, 3]

Initial: slow=0, fast=1
[1, 1, 2, 2, 3]
S F
fast=1: nums[1]=1 == nums[slow]=1
Same → skip
fast=2: nums[2]=2 != nums[slow]=1
Different → slow++, copy
[1, 2, 2, 2, 3]
S F
fast=3: nums[3]=2 == nums[slow]=2
Same → skip
fast=4: nums[4]=3 != nums[slow]=2
Different → slow++, copy
[1, 2, 3, 2, 3]
S F
Result: length = slow + 1 = 3
[1, 2, 3, _, _]
Code
Java
9.

Remove Element

Remove all occurrences of a value IN-PLACE. Return new length.

Simpler than Remove Duplicates:

  • Just skip elements equal to val
  • Copy everything else to slow position

Example: nums = [3, 2, 2, 3], val = 3

Initial: slow=0, fast=0
[3, 2, 2, 3]
SF
fast=0: nums[0]=3 == val → skip
fast=1: nums[1]=2 != val → copy, slow++
[2, 2, 2, 3]
S F
fast=2: nums[2]=2 != val → copy, slow++
[2, 2, 2, 3]
S F
fast=3: nums[3]=3 == val → skip
Result: [2, 2, _, _], length = 2
Code
Java
10.

Move Zeroes

Move all zeroes to end while maintaining relative order of non-zeroes.

Two Approaches:

Approach 1: Copy then fill

1. Copy all non-zeroes to front (same direction pointers)
2. Fill rest with zeroes

Approach 2: Swap

When fast finds non-zero, swap with slow position
Maintains relative order!

Example: [0, 1, 0, 3, 12]

Using swap approach:
Initial: [0, 1, 0, 3, 12]
SF
fast=0: 0 → skip
fast=1: 1 → swap(slow, fast), slow++
[1, 0, 0, 3, 12]
S F
fast=2: 0 → skip
fast=3: 3 → swap(slow, fast), slow++
[1, 3, 0, 0, 12]
S F
fast=4: 12 → swap(slow, fast), slow++
[1, 3, 12, 0, 0]
S
Code
Java
11.

3Sum: Loop + Two Pointers

Find ALL unique triplets that sum to zero. Combines outer loop with two pointers.

Algorithm:

  1. Sort the array
  2. Fix first element with outer loop (i)
  3. Use two pointers for remaining pair (left, right)
  4. Skip duplicates at ALL levels to avoid duplicate triplets

Why Sort?

  • Enables two pointers for inner pair
  • Makes duplicate skipping easy (identical elements are adjacent)

Duplicate Handling (CRITICAL):

// Skip duplicate first elements
if (i > 0 && nums[i] == nums[i-1]) continue;
// Skip duplicate second elements (after finding a triplet)
while (left < right && nums[left] == nums[left+1]) left++;
// Skip duplicate third elements
while (left < right && nums[right] == nums[right-1]) right--;
Code
Java
12.

4Sum: Extending the Pattern

Find all unique quadruplets that sum to target. Same pattern, one more loop!

Pattern Extension:

2Sum: Two pointers O(n)
3Sum: 1 loop + Two pointers O(n²)
4Sum: 2 loops + Two pointers O(n³)
kSum: (k-2) loops + Two pointers O(n^(k-1))

4Sum Algorithm:

  1. Sort the array
  2. First loop fixes first element (i)
  3. Second loop fixes second element (j)
  4. Two pointers find remaining pair
  5. Skip duplicates at ALL levels
Code
Java
13.

Valid Palindrome

Check if string is palindrome (alphanumeric only, ignore case).

Two Pointer Approach:

  1. Start from both ends
  2. Skip non-alphanumeric characters
  3. Compare characters (case-insensitive)
  4. If mismatch found → not palindrome
  5. If pointers meet → palindrome

Example: "A man, a plan, a canal: Panama"

After cleaning: "amanaplanacanalpanama"
left=0, right=20: 'a' == 'a' ✓
left=1, right=19: 'm' == 'm' ✓
...
left=10, right=10: pointers meet → Palindrome!
Code
Java
14.

Valid Palindrome II (One Deletion)

Can string become palindrome by deleting at most ONE character?

Algorithm:

  1. Use two pointers from both ends
  2. If mismatch found, try deleting left OR deleting right
  3. Check if either resulting substring is palindrome

Example: "abca"

left=0, right=3: 'a' == 'a' ✓
left=1, right=2: 'b' != 'c' ✗
Try deleting 'b': check "aca" → palindrome? Yes!
Result: true
Code
Java
15.

Dutch National Flag (Sort Colors)

Sort array with only 0s, 1s, and 2s using THREE pointers.

The Three Regions:

[0, 0, 0, | 1, 1, 1, | ?, ?, ? | 2, 2, 2]
low mid high
- Everything before low is 0
- Everything between low and mid is 1
- Everything after high is 2
- Between mid and high is unexplored

Rules:

  • nums[mid] == 0: swap with low, advance both
  • nums[mid] == 1: just advance mid
  • nums[mid] == 2: swap with high, only decrement high (don't advance mid!)

Why Not Advance Mid After Swapping with High? The swapped value from high is UNKNOWN - we need to examine it!

Code
Java
16.

Squares of Sorted Array

Given sorted array (may have negatives), return squares in sorted order.

Key Insight: Largest squares are at the ENDS (most negative or most positive).

Algorithm: Use opposite direction pointers, build result from END to START.

Example: [-4, -1, 0, 3, 10]

left=0 (-4²=16), right=4 (10²=100)
100 > 16 → result[4] = 100, right--
left=0 (-4²=16), right=3 (3²=9)
16 > 9 → result[3] = 16, left++
left=1 (-1²=1), right=3 (3²=9)
9 > 1 → result[2] = 9, right--
... continue ...
Result: [0, 1, 9, 16, 100]
Code
Java
17.

Common Edge Cases

Master these to avoid interview pitfalls.

1. Empty Array

if (nums.length == 0) return ...;

2. Single Element

if (nums.length == 1) return nums[0]; // or appropriate result

3. All Same Elements

[5, 5, 5, 5] - duplicates handling critical

4. All Zeros / All Target Value

moveZeroes([0, 0, 0]) → [0, 0, 0]
removeElement([3, 3, 3], 3) → length 0

5. Already Sorted Result

removeDuplicates([1, 2, 3]) → no changes needed

6. Negative Numbers

sortedSquares([-3, -1, 0, 1, 2]) - negatives become large positives!

7. Integer Overflow

// For 4Sum with large numbers
long sum = (long)nums[i] + nums[j]; // Use long!

8. Loop Condition

while (left < right) // Opposite direction (don't process same element twice)
while (left <= right) // Some problems need to process when equal
18.

Pattern Recognition Guide

Quick reference for choosing the right approach:

Problem TypeApproachTime
Two Sum (sorted)Opposite directionO(n)
3SumLoop + oppositeO(n²)
4Sum2 loops + oppositeO(n³)
Container WaterOpposite, move shorterO(n)
Trapping Rain WaterOpposite, track maxesO(n)
Remove DuplicatesSame directionO(n)
Remove ElementSame directionO(n)
Move ZeroesSame direction (swap)O(n)
Sort ColorsDutch National FlagO(n)
PalindromeOpposite directionO(n)
Squares of SortedOpposite, fill from endO(n)

Decision Tree:

Is array sorted (or can sort)?
├── YES
│ ├── Finding pair/triplet with sum? → Opposite direction
│ ├── Modifying in-place? → Same direction
│ ├── Partitioning 3 values? → Dutch National Flag
│ └── Checking palindrome? → Opposite direction
└── NO
└── Consider: O(n log n) sort + O(n) two pointers
vs O(n²) brute force
19.

Complexity Analysis

Time Complexity:

  • Single pass two pointers: O(n)
  • With sorting: O(n log n) + O(n) = O(n log n)
  • Nested with two pointers (3Sum): O(n²)
  • Double nested (4Sum): O(n³)

Space Complexity:

  • In-place modifications: O(1)
  • If sorting creates copy: O(n) or O(log n) for sort

Why Two Pointers Achieves O(n):

Opposite Direction:
- left only moves right (increases)
- right only moves left (decreases)
- Combined: at most n moves total
- Each element visited at most once
Same Direction:
- fast visits every element: n iterations
- slow only moves forward (never backward)
- Total work: O(n)

Comparison to Brute Force:

Two Sum (sorted):
- Brute force: O(n²) - check all pairs
- Two pointers: O(n) - skip invalid pairs intelligently
The key: Sorted order gives INFORMATION!
If sum > target, ALL pairs with current right are too big.
Skip them all at once!
20.

Template Summary

Copy-paste ready templates.

Template 1: Opposite Direction

int left = 0, right = arr.length - 1;
while (left < right) {
if (/* found */) return result;
else if (/* need bigger */) left++;
else right--;
}

Template 2: Same Direction (Filter)

int slow = 0;
for (int fast = 0; fast < arr.length; fast++) {
if (/* keep this element */) {
arr[slow++] = arr[fast];
}
}
return slow; // new length

Template 3: Same Direction (Remove Duplicates)

int slow = 0;
for (int fast = 1; fast < arr.length; fast++) {
if (arr[fast] != arr[slow]) {
arr[++slow] = arr[fast];
}
}
return slow + 1;

Template 4: Dutch National Flag

int low = 0, mid = 0, high = n - 1;
while (mid <= high) {
if (arr[mid] == 0) swap(low++, mid++);
else if (arr[mid] == 1) mid++;
else swap(mid, high--);
}

Template 5: 3Sum Pattern

Arrays.sort(nums);
for (int i = 0; i < n - 2; i++) {
if (i > 0 && nums[i] == nums[i-1]) continue;
int left = i + 1, right = n - 1;
while (left < right) {
// Two pointer logic for remaining pair
}
}

Test Your Knowledge

Take a quick 15question quiz to test what you've learned.