Two Pointers Technique in Java — A Practical Guide for Serious Engineers
Two Pointers is one of the most effective techniques for turning brute-force array/string solutions into linear-time solutions.
If you build backend systems, this matters for the same reason it matters in interviews: you learn to manage state and constraints precisely, with minimal memory overhead and predictable performance.
Why Do We Need Two Pointers?
Many array and string problems involve:
- Pair comparisons
- Subarray evaluation
- Partitioning and compaction
- Deduplication
- Reversal and symmetry checks
A naive approach often uses nested loops:
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
// check condition
}
}
Time complexity: O(n²)
Two pointers often reduces this to O(n) by moving indices strategically.
Core Idea
Maintain two indices and move them with a rule (an invariant) that guarantees progress.
Common patterns:
- Opposite direction:
leftfrom start,rightfrom end (often sorted arrays / symmetry problems) - Same direction (fast/slow):
fastscans,slowcompacts/builds answer in-place - Sliding window: expand with
right, shrink withleft(subset of two pointers, but deserves its own deep dive)
Pattern 1: Opposite Direction (Sorted Array)
Problem: Two Sum in a Sorted Array
Given a sorted array, find two numbers whose sum equals target.
Efficient Solution (O(n), O(1))
public int[] twoSumSorted(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
return new int[]{left, right};
} else if (sum < target) {
left++;
} else {
right--;
}
}
return new int[]{-1, -1};
}
Why It Works (Invariant Thinking)
Because the array is sorted:
- If
nums[left] + nums[right]is too small, the only way to increase it is to moveleftrightward. - If it’s too large, the only way to decrease it is to move
rightleftward.
Invariant: At any time, all pairs outside [left, right] have already been ruled out.
Real-World Backend Example: Risk/Fraud Pair Detection on Sorted Streams
Imagine a fraud rule:
“Flag if two transactions by the same user within a short time window exceed a threshold.”
If transactions are sorted by timestamp:
leftmarks the start of the current time windowrightscans forward- as
rightmoves, advanceleftto keep the window valid
This avoids comparing every pair (O(n²)) and is scalable for large streams.
Pattern 2: Fast & Slow Pointer (In-Place Compaction)
This pattern is used to:
- Remove duplicates
- Remove elements matching a predicate
- Partition arrays in-place
- Reduce memory allocations
Example: Remove Duplicates (Sorted Array)
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int slow = 0; // last unique index
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
Time: O(n)
Extra space: O(1)
Invariant: nums[0..slow] is always the “unique compacted prefix”.
Pattern 3: Two Pointers Over Strings (Symmetry / Palindrome)
Classic: validate palindrome by moving inward.
Example: Valid Palindrome (Ignoring Non-Alphanumerics)
public boolean isPalindrome(String s) {
int left = 0;
int right = s.length() - 1;
while (left < right) {
char a = s.charAt(left);
char b = s.charAt(right);
if (!Character.isLetterOrDigit(a)) {
left++;
continue;
}
if (!Character.isLetterOrDigit(b)) {
right--;
continue;
}
if (Character.toLowerCase(a) != Character.toLowerCase(b)) {
return false;
}
left++;
right--;
}
return true;
}
Architecture Perspective (Why Backend Engineers Should Care)
Two pointers is more than a “DSA trick”:
- In-place transformation → fewer allocations → less GC pressure
- Cache-friendly sequential scans → better CPU locality than random-access-heavy approaches
- Predictable performance → stable latency under load
If you’re processing large arrays, payload buffers, logs, or pre-sorted datasets, this technique is a practical optimization lever.
When Two Pointers Should Be Your First Thought
Try it first when:
- The input is sorted (or you can sort without breaking constraints)
- You need pairs/triplets with a condition (sum, distance, bounds)
- You must modify data in place
- The problem asks for the “longest/shortest subarray/substring with constraints” (often becomes sliding window)
Pros and Cons
Pros
- Often reduces O(n²) → O(n)
- Usually O(1) extra memory
- Encourages clean invariants and simpler state
- Great foundation for sliding window and partition problems
Cons
- Easy to get boundary conditions wrong
- Some variants require sorted input (or sorting changes meaning)
- Debugging is harder without explicit invariants
Common Mistakes
- Moving the wrong pointer (breaks progress / misses solutions)
- Incorrect loop condition (
left <= rightvsleft < right) - Forgetting to handle duplicates correctly (e.g., 3Sum)
- Mixing responsibilities (pointer movement + business logic) without invariants
Best Practices
- Write the invariant as a comment
- Example:
// invariant: nums[0..slow] are unique
- Example:
- Guarantee progress every loop
- Every iteration must move
left,right,fast, orslow.
- Every iteration must move
- Be strict about preconditions
- If the solution depends on sorted input, state it early.
- Keep pointer logic small
- Extract helper methods when dealing with skipping rules (e.g., alphanumeric filter).
Top Two-Pointer Problems to Master (with External Links)
These problems cover the most useful two-pointer variants:
-
Two Sum II — Input Array Is Sorted (LC 167)
LeetCode -
3Sum (LC 15) — sorting + opposite-direction pointers + duplicate handling
LeetCode -
Container With Most Water (LC 11) — greedy pointer movement
LeetCode
GeeksforGeeks -
Trapping Rain Water (LC 42) — two-pointer with prefix-like reasoning
LeetCode
GeeksforGeeks -
Remove Duplicates from Sorted Array (LC 26) — fast/slow compaction
LeetCode
GeeksforGeeks -
Valid Palindrome (LC 125) — inward pointers + skipping rules
LeetCode
GeeksforGeeks (palindrome with two pointers)
Conclusion: Key Takeaways
- Two pointers is a disciplined way to reason about constraints with minimal state.
- The “secret” is not the pointers — it’s the invariant and progress rule.
- Master the core patterns (opposite-direction, fast/slow, string symmetry) and you’ll recognize them everywhere.
If you want to get truly strong, practice the 6 problems above until you can implement each variant quickly and correctly in Java.
Author: Sandeep Bhardwaj
Senior Backend Engineer | Java | Distributed Systems