You've probably seen solutions that say "just use two pointers," as if that explains everything.
But when you face a new LeetCode problem, it’s not obvious:
- Where should the pointers start?
- When do they move together vs in opposite directions?
- How do you know you’re not skipping valid answers?
This guide is a step-by-step, intuition-first walkthrough of the two pointers technique for LeetCode beginners, with diagrams, examples, and a simple mental checklist you can reuse.
TL;DR
- Two pointers is about tracking a relationship between two positions in an array/string: moving them in a controlled way to avoid brute-force nested loops.
- Common patterns: opposite ends (e.g., 2-sum on sorted arrays), sliding window (left/right in same direction), and fast/slow pointers (linked lists, cycles).
- The core steps: identify the invariant you want to maintain, choose pointer starts, decide movement rules, and stop once all candidates are explored.
- Beginners often move pointers without a clear invariant, forget about sorted requirements, or miss edge cases where pointers cross.
- With a small set of templates, visual diagrams, and tools like AI-guided LeetCode practice, two pointers becomes a go-to technique, not a mysterious trick.
What Is the Two Pointers Technique, Really?
Intuition: Shrinking the search space
Naive approaches often check all pairs:
for i in [0..n-1]:
for j in [i+1..n-1]:
check(nums[i], nums[j])That’s O(n²) comparisons.
Two pointers replaces this with smart movement:
- Instead of checking every pair, you maintain an invariant and move pointers in ways that eliminate many pairs at once.
- You use structure (sorted order, or window constraints) to avoid rechecking work.
Visual: Opposite ends pattern
For a sorted array:
Index: 0 1 2 3 4
Nums: [1, 2, 4, 7, 11]
^ ^
left rightYou:
- Compute
sum = nums[left] + nums[right]. - Move left rightward or right leftward depending on whether sum is too small or too big.
- Eliminate whole regions of pairs with one move.
Step-by-Step Framework: How to Design a Two-Pointer Solution
When you suspect two pointers might help, walk through this checklist.
Step 1: Look for structure in the data
Ask:
- Is the array sorted or can it be sorted?
- Are we dealing with a contiguous range (subarrays/substrings)?
- Are we scanning a linked list?
Two pointers usually needs one of:
- Sorted order (for opposite-ends pointers).
- Contiguous segments (for sliding windows).
- Next pointers (for fast/slow linked list pointers).
Step 2: Define the relationship between pointers
Typical relationships:
left < rightand we consider the pair(left, right).leftandrightdefine a window[left..right].slowandfastmove at different speeds along a list.
Write down the invariant:
- For opposite ends: “All possible pairs are covered without duplicates.”
- For sliding window: “Window
[left..right]satisfies condition X.” - For fast/slow: “If there is a cycle, fast will eventually meet slow.”
Step 3: Decide movement rules
Ask for each step:
- When do I move
left? - When do I move
right? - When do I stop?
This should preserve your invariant and eventually cover all meaningful candidates.
Example 1: Two Sum in a Sorted Array (Opposite Ends)
Problem: Given a sorted array and a target, return indices of two numbers that sum to target.
Why two pointers works here
- Array is sorted → if the sum is too small, moving the left pointer right increases the sum; if too big, moving the right pointer left decreases it.
- You can eliminate many pairs in one move instead of trying all
O(n²)pairs.
Code example (TypeScript)
function twoSumSorted(nums: number[], target: number): number[] {
let left = 0;
let right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) {
return [left, right];
} else if (sum < target) {
left++; // need a larger sum
} else {
right--; // need a smaller sum
}
}
return [-1, -1]; // or throw error if guaranteed to exist
}Visual trace
nums = [1, 2, 4, 7, 11], target = 13
left=0 (1), right=4 (11), sum=12 < 13 → move left
left=1 (2), right=4 (11), sum=13 == 13 → answerInvariant:
At each step, if a pair exists, it lies within
[left..right].
Example 2: Removing Duplicates from a Sorted Array (Same-Direction Pointers)
Problem: Given a sorted array, remove duplicates in-place and return the length of the deduplicated prefix.
Intuition
- Use
slowpointer to mark the end of the deduplicated prefix. - Use
fastpointer to scan the array; when you find a new value, extend the prefix.
Visual
nums = [1, 1, 2, 2, 3]
slow points to last unique
fast scans next elementCode sketch
function removeDuplicates(nums: number[]): number {
if (nums.length === 0) return 0;
let slow = 0;
for (let fast = 1; fast < nums.length; fast++) {
if (nums[fast] !== nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}Invariant:
Elements in
nums[0..slow]are unique and in sorted order.
This is the same conceptual idea as a sliding window—[0..slow] is the valid region you maintain.
Visual “Diagram” for Two Pointers
You can sketch a simple line:
Index: 0 1 2 3 4 5
Array: [a, b, c, d, e, f]
^ ^
left rightAsk yourself:
- What does the segment between them mean?
- What happens to that meaning when I move one pointer?
Tools like LeetCopilot can support you by showing pointer movement step by step, especially when you’re debugging off-by-one behavior in your two-pointer loops.
Practical Preparation Strategies
Strategy 1: Group problems by two-pointer pattern
Instead of mixing everything, cluster:
- Opposite-ends problems (sorted arrays, container with most water).
- Same-direction / slow-fast problems (deduplication, partitioning).
- Sliding window problems (overlap with two pointers).
In your DSA learning path, tag them accordingly.
Strategy 2: Write invariants as comments
Before coding:
# Invariant: subarray nums[left..right] has at most k distinct characters
# Invariant: if pair exists, it's within [left..right]This habit makes your pointer moves more deliberate and easier to debug.
Strategy 3: Practice dry runs on paper
Take a small array and trace:
- How do
leftandrightmove? - Which indices are skipped?
- Do you cover every relevant candidate pair or window?
Common Mistakes to Avoid
Mistake 1: Using two pointers without required structure
If the array isn’t sorted (and sorting breaks the problem), or if you’re not working with contiguous regions, two pointers may not be appropriate.
Mistake 2: Updating both pointers incorrectly
Randomly moving both pointers at once often loses candidates.
Fix: Move one pointer at a time based on a clear rule; think through what region you’re eliminating.
Mistake 3: Infinite loops or skipping termination
Always make sure each iteration progresses:
leftincreases orrightdecreases (opposite ends).fastadvances (fast/slow).- Eventually, the loop condition fails.
Mistake 4: Off-by-one in sliding windows
Window size is typically right - left + 1. Forgetting the +1 leads to subtle bugs.
FAQ
Q1: How do I know a problem wants two pointers?
Look for structure: sorted arrays, contiguous subarrays, or linked lists. Phrases like “find pair”, “min/max subarray”, “in-place without extra space” are strong hints.
Q2: What’s the difference between two pointers and sliding window?
Sliding window is a specific use of two pointers where left and right move in the same direction and define a window with some invariant. Opposite-ends two pointers is more about shrinking from both sides in a sorted structure.
Q3: Can I always sort the array to use two pointers?
Not always. If the problem cares about original indices or order, sorting may break the requirements. If you sort, be explicit about how you’ll map back to original positions.
Q4: How should I practice this pattern?
Pick 8–10 problems across different categories (pairs, windows, slow/fast). For each, write the invariant explicitly and try re-implementing from memory. Tools like AI-guided LeetCode practice can provide hint-only guidance to reinforce the pattern without spoiling it.
Q5: What about linked list cycle problems?
Fast/slow pointers (Floyd’s algorithm) is another two-pointer pattern: if a cycle exists, fast eventually meets slow. Practice it separately and note how the invariant there is about meeting points, not windows.
Conclusion
Two pointers is not a magical trick—it’s a way of exploiting structure to avoid brute-force pair checking.
The core mindset:
- Identify the structure (sorted, window, list).
- Define what your two pointers represent and what invariant they maintain.
- Move them according to simple, predictable rules.
With enough clustered practice, two-pointer solutions will start to feel like natural first choices rather than clever afterthoughts.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
