Hey everyone! While most people focus on learning complex algorithms like DP and graphs, they often overlook one of the simplest yet most powerful patterns - Two Pointers. This might be because it seems too basic, or people don't realize how versatile it is. However, mastering two pointers can help you solve 15-20% of all interview problems with elegant O(n) solutions. It's one of the highest ROI patterns you can learn! So let me show you why two pointers deserves way more respect than it gets.
When to use: Sorted array, palindrome checking, pair sum problems
Movement: Start from both ends, move toward center
Template:
int left = 0, right = arr.length - 1;
while (left < right) {
// Process arr[left] and arr[right]
if (condition) {
left++;
} else {
right--;
}
}Classic Problems:
Why it works: When array is sorted or has symmetric property, we can eliminate half the search space with each decision.
When to use: Remove elements, in-place modifications, partitioning
Movement: Both move forward, one faster than the other
Template:
int slow = 0;
for (int fast = 0; fast < arr.length; fast++) {
if (shouldKeep(arr[fast])) {
arr[slow] = arr[fast];
slow++;
}
}
return slow; // new lengthClassic Problems:
Why it works: Fast pointer explores, slow pointer maintains the valid portion.
When to use: Subarray/substring with constraints, variable window size
Movement: Expand right, shrink left when condition violated
Template:
int left = 0, right = 0;
while (right < arr.length) {
// Add arr[right] to window
right++;
while (windowInvalid) {
// Remove arr[left] from window
left++;
}
// Update result with current window
}Classic Problems:
Why it works: Maintains a valid window by expanding and contracting dynamically.
When to use: Sort into groups, 3-way partitioning
Movement: Three pointers - low, mid, high
Template:
int low = 0, mid = 0, high = arr.length - 1;
while (mid <= high) {
if (arr[mid] == 0) {
swap(arr, low, mid);
low++;
mid++;
} else if (arr[mid] == 1) {
mid++;
} else {
swap(arr, mid, high);
high--;
}
}Classic Problems:
Why it works: Maintains three regions - processed, current, and unprocessed.
When to use: Merging sorted sequences, finding common elements
Movement: One pointer per array, advance smaller element
Template:
int i = 0, j = 0;
while (i < arr1.length && j < arr2.length) {
if (arr1[i] < arr2[j]) {
// Process arr1[i]
i++;
} else if (arr1[i] > arr2[j]) {
// Process arr2[j]
j++;
} else {
// Both equal
i++;
j++;
}
}
// Handle remaining elementsClassic Problems:
Why it works: Sorted property allows us to always know which element to process next.
When to use: Finding pairs/triplets with target sum
Movement: Fix one, use two pointers for remaining
Template:
// For 3Sum
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == target) {
// Found triplet
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}Classic Problems:
Why it works: Sorted array + two pointers eliminates duplicate work.
When to use: In-place array/string reversal, rotation
Movement: Swap from both ends moving toward center
Template:
void reverse(int[] arr, int start, int end) {
while (start < end) {
swap(arr, start, end);
start++;
end--;
}
}
// Rotate array: reverse(0, k-1), reverse(k, n-1), reverse(0, n-1)Classic Problems:
Why it works: Swapping from ends naturally reverses order.
When to use: Area/volume calculations, optimization problems
Movement: Two pointers with auxiliary variables tracking max/min
Template:
int left = 0, right = arr.length - 1;
int leftMax = 0, rightMax = 0, result = 0;
while (left < right) {
if (arr[left] < arr[right]) {
if (arr[left] >= leftMax) {
leftMax = arr[left];
} else {
result += leftMax - arr[left];
}
left++;
} else {
if (arr[right] >= rightMax) {
rightMax = arr[right];
} else {
result += rightMax - arr[right];
}
right--;
}
}Classic Problems:
Why it works: Process from both ends, always move the pointer with smaller value.
When to use: Check if one sequence is subsequence of another
Movement: One pointer per string, advance first when match found
Template:
int i = 0; // pointer for subsequence
for (int j = 0; j < str.length() && i < subseq.length(); j++) {
if (str.charAt(j) == subseq.charAt(i)) {
i++;
}
}
return i == subseq.length(); // all matchedClassic Problems:
Why it works: Greedily match characters in order they appear.
When to use: Merging overlapping intervals, range operations
Movement: Iterate with pointer, merge based on overlap
Template:
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
List<int[]> result = new ArrayList<>();
int[] current = intervals[0];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= current[1]) {
// Overlap - merge
current[1] = Math.max(current[1], intervals[i][1]);
} else {
// No overlap
result.add(current);
current = intervals[i];
}
}
result.add(current);Classic Problems:
Why it works: Sorted intervals allow sequential merging.
Master two pointers, and you'll solve problems others struggle with using complex hash maps or nested loops - with cleaner code and better complexity!
Check out my posts which may help you in your preparation :
What's your favorite two pointer problem? Or which variation do you find trickiest? Let's discuss! 👇