Two Pointers (Same Direction)
Source: Dev.to
Overview
The Two Pointers (Same Direction) pattern uses two indices moving forward together through a data structure (usually an array or string).
Unlike opposite‑direction pointers, both pointers only move left → right, but at different speeds or based on conditions.
This pattern is extremely common in linear‑time optimizations.
When to Use Two Pointers (Same Direction)
Use this pattern when:
- You process elements in order.
- You want to compare or relate two positions.
- You need to skip, filter, or track a range.
- You want to avoid nested loops (O(N²)).
Typical Problem Statements
- Remove duplicates from a sorted array
- Check if an array is a subsequence of another
- Longest substring without repeating characters
- Merge two sorted arrays
- Partition an array based on a condition
- Fast & slow pointer problems (variant)
Core Intuition
Think of the pointers as:
- Reader & Writer
- Slow & Fast
- Anchor & Explorer
One pointer represents a reference position, the other scans ahead.
General Algorithm Template
slow = 0
for fast in range(0, n):
if condition_satisfied:
# perform operation using slow & fast
slow += 1
fastmoves every iteration.slowmoves only when needed.
Key Properties
- Time Complexity: O(N)
- Space Complexity: O(1)
- Works best on sorted arrays or strings.
- Avoids unnecessary comparisons.
Example 1 – Remove Duplicates from Sorted Array
Problem
Given a sorted array, remove duplicates in‑place and return the new length.
Idea
fast scans elements, slow marks the position to write unique values.
Code
def remove_duplicates(nums):
if not nums:
return 0
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
Example 2 – Check Subsequence
Problem
Check if string s is a subsequence of string t.
Idea
slow tracks position in s, fast scans t.
Code
def is_subsequence(s, t):
slow = 0
for fast in range(len(t)):
if slow < len(s) and s[slow] == t[fast]:
slow += 1
return slow == len(s)
Example 3 – Longest Substring Without Repeating Characters
Problem
Find the length of the longest substring without repeating characters.
Idea
Both left and right move forward; a set maintains uniqueness.
Code
def longest_unique_substring(s):
seen = set()
left = 0
max_len = 0
for right in range(len(s)):
while s[right] in seen:
seen.remove(s[left])
left += 1
seen.add(s[right])
max_len = max(max_len, right - left + 1)
return max_len
Example 4 – Merge Two Sorted Arrays
Problem
Merge two sorted arrays into one sorted array.
Idea
Pointer i for the first array, pointer j for the second array; both move forward.
Code
def merge_sorted(a, b):
i = j = 0
result = []
while i < len(a) and j < len(b):
if a[i] <= b[j]:
result.append(a[i])
i += 1
else:
result.append(b[j])
j += 1
result.extend(a[i:])
result.extend(b[j:])
return result
Difference from Sliding Window
| Feature | Two Pointers (Same Direction) | Sliding Window |
|---|---|---|
| Window Concept | ❌ Optional | ✅ Required |
| Condition | Simple comparison | Constraint‑based |
| Use Case | Filtering / matching | Optimization |
| Typical Example | Remove duplicates | Longest subarray |
Common Mistakes
- Moving both pointers blindly.
- Forgetting pointer bounds.
- Using it for unsorted data when order matters.
- Confusing it with opposite‑direction pointers.
How to Identify This Pattern
Ask yourself:
- Do I need to scan forward once?
- Can one pointer track progress while the other scans ahead?
- Is order important?
If you can replace a nested loop with such a scan, the pattern applies.
Mental Model
Imagine:
- One pointer walks forward.
- Another pointer records a reference.
- Both move forward, never backward.
Summary
| Aspect | Two Pointers (Same Direction) |
|---|---|
| Pointers | Both move left → right |
| Speed | Different (fast vs. slow) |
| Time | O(N) |
| Space | O(1) |
| Best For | Filtering, matching, merging |
Final Thought
This pattern bridges the gap between brute‑force and optimal solutions.
Once mastered, you’ll instinctively replace nested loops with clean linear scans.