Two pointers is a fundamental technique that solves a surprising range of interview problems efficiently. When done right, it transforms O(n²) brute-force solutions into elegant O(n) or O(n log n) solutions. The challenge: recognizing when to use two pointers and which variant applies. This guide covers the pattern, variations, and problems where two pointers shines.
The Two Pointers Mindset
Two pointers works when:
- You have a sequence (array or linked list)
- You need to find a pair or remove elements or partition data
- The sequence has some ordering or structure you can exploit
The core idea: Use two pointers moving in coordinated ways to explore the space efficiently.
Pattern 1: Two Pointers on Sorted Array (Converging)
Start with one pointer at the beginning, one at the end. Move them toward each other.
When to use: Find a pair with a specific sum, remove duplicates, partition into sections.
Classic Problem: Two Sum II (Sorted Array) Find two numbers that sum to a target.
def two_sum(numbers, target):
left, right = 0, len(numbers) - 1
while left < right:
current_sum = numbers[left] + numbers[right]
if current_sum == target:
return [left + 1, right + 1] # 1-indexed
elif current_sum < target:
left += 1 # Need larger sum
else:
right -= 1 # Need smaller sum
return []Time: O(n), Space: O(1)
Why it works: Array is sorted. If sum is too small, left element is too small; move left forward. If sum is too large, right element is too large; move right backward.
Variation: 3Sum Find three numbers that sum to target.
def three_sum(nums, target):
nums.sort()
result = []
for i in range(len(nums) - 2):
# Two sum on the rest
left, right = i + 1, len(nums) - 1
while left < right:
current_sum = nums[i] + nums[left] + nums[right]
if current_sum == target:
result.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1
elif current_sum < target:
left += 1
else:
right -= 1
return resultPattern 2: Sliding Window
Use two pointers to maintain a window that expands and contracts based on a condition.
When to use: Longest/shortest substring with property, character frequency problems, subarray sums.
Classic Problem: Longest Substring Without Repeating Characters
def length_of_longest_substring(s):
char_index = {}
max_length = 0
left = 0
for right in range(len(s)):
if s[right] in char_index and char_index[s[right]] >= left:
# Character already in window, move left
left = char_index[s[right]] + 1
char_index[s[right]] = right
max_length = max(max_length, right - left + 1)
return max_lengthTime: O(n), Space: O(min(n, alphabet_size))
Why it works: Right pointer expands the window. When a duplicate is found, left pointer contracts until the duplicate is out. The window always contains unique characters.
Variation: Maximum Window Sum Find a subarray of size k with maximum sum.
def max_sum_subarray(nums, k):
if k > len(nums):
return None
window_sum = sum(nums[:k])
max_sum = window_sum
for i in range(k, len(nums)):
window_sum = window_sum - nums[i - k] + nums[i]
max_sum = max(max_sum, window_sum)
return max_sumVariation: Minimum Window Substring Given s and t, find the minimum substring of s that contains all characters of t.
def min_window(s, t):
if len(t) > len(s):
return ""
t_count = {}
for char in t:
t_count[char] = t_count.get(char, 0) + 1
required = len(t_count)
left, right = 0, 0
formed = 0
window_counts = {}
ans = float('inf'), None, None
while right < len(s):
char = s[right]
window_counts[char] = window_counts.get(char, 0) + 1
if char in t_count and window_counts[char] == t_count[char]:
formed += 1
while left <= right and formed == required:
char = s[left]
if right - left + 1 < ans[0]:
ans = (right - left + 1, left, right)
window_counts[char] -= 1
if char in t_count and window_counts[char] < t_count[char]:
formed -= 1
left += 1
right += 1
return "" if ans[0] == float('inf') else s[ans[1]:ans[2] + 1]Pattern 3: Fast and Slow Pointers
Move one pointer faster than the other. Useful for linked list problems.
When to use: Detect cycles, find middle, find k-th element from end.
Classic Problem: Detect Cycle in Linked List
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return FalseWhy it works: If there's a cycle, fast pointer eventually catches slow pointer.
Variation: Find Cycle Start
def detect_cycle(head):
slow = fast = head
# Find cycle
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
break
else:
return None # No cycle
# Find cycle start
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slowWhy it works: When they meet, if you reset one pointer to head and move both one step at a time, they meet at cycle start.
Variation: Middle of Linked List
def find_middle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slowPattern 4: Two Pointers Moving in Same Direction
Both pointers move forward, but at different rates or conditions.
Classic Problem: Container with Most Water Given array of heights, find two lines that form a container with max area.
def max_area(height):
left, right = 0, len(height) - 1
max_area_val = 0
while left < right:
# Width and height of current container
width = right - left
h = min(height[left], height[right])
area = width * h
max_area_val = max(max_area_val, area)
# Move the pointer pointing to smaller height
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_area_valWhy it works: Moving the taller line won't improve area (limited by shorter line). Only moving the shorter line can improve area.
Pattern 5: Remove Duplicates/Unwanted Elements
Use two pointers to partition array in-place.
Classic Problem: Remove Duplicates from Sorted Array
def remove_duplicates(nums):
if not nums:
return 0
write = 0
for read in range(1, len(nums)):
if nums[read] != nums[write]:
write += 1
nums[write] = nums[read]
return write + 1Why it works: Read pointer scans all elements. Write pointer marks where unique elements go. Overwrite duplicates.
Variation: Remove Element Remove all instances of a value in-place.
def remove_element(nums, val):
left = 0
for right in range(len(nums)):
if nums[right] != val:
nums[left] = nums[right]
left += 1
return leftPattern 6: Partition into Two Parts
Rearrange array so elements meeting a condition are on one side.
Classic Problem: Move Zeros to End
def move_zeros(nums):
left = 0
for right in range(len(nums)):
if nums[right] != 0:
nums[left], nums[right] = nums[right], nums[left]
left += 1Common Two Pointers Problems
- Palindrome check: Two pointers from ends.
- Trapping rain water: Two pointers, track max heights.
- Squares of sorted array: Two pointers (largest squares are at ends).
- Merge sorted arrays: Two pointers, merge from both arrays.
- Reverse array: Two pointers, swap from ends.
Time and Space Complexity
- Two pointers on sorted array: O(n) time, O(1) space
- Sliding window: O(n) time (each element visited twice at most), O(k) space (window size)
- Fast/slow pointers: O(n) time, O(1) space
This is why two pointers is powerful: near-linear time with minimal extra space.
Interview Tips
- Identify the structure. Is it a sorted array? Linked list? Subarray problem?
- Choose the pattern. Converging, diverging, or same-direction movement?
- Clearly state your approach. "I'll use two pointers, one from each end, moving toward center..."
- Handle edge cases. Empty arrays, single elements, all duplicates.
- Test on small examples. Trace through your algorithm step-by-step.
Practicing Two Pointers
- Do 15-20 two pointer problems on LeetCode.
- For each, identify which pattern it uses.
- Code without looking at solutions.
- Optimize to O(n) time and O(1) space.
- Solve it multiple ways if possible.
Real Interview Scenario
Interviewer: "Given a sorted array, find two numbers that sum to a target."
Your approach:
- "This is a two pointers problem. I'll start with one pointer at the beginning and one at the end."
- "If the sum is too small, I move the left pointer right (to increase sum)."
- "If the sum is too large, I move the right pointer left (to decrease sum)."
- "When they meet or I find the target, I'm done."
- Code it up, test on examples, analyze complexity.
Interviewer: "What if the array isn't sorted?"
Your response: "I could sort it first (O(n log n)), or use a hash map for O(n) time and O(n) space. Two pointers wouldn't work there."
Mastering Two Pointers
Two pointers is elegant and powerful. It's also one of the first techniques that separates mediocre engineers from good ones. Most candidates don't recognize two pointer problems quickly. If you master this technique, you'll solve problems in seconds that others struggle with for minutes.
Start with simple converging pointers, then move to sliding windows, then fast/slow pointers. Within a week of practice, you'll have this down.
When you're practicing and want real-time feedback on whether your two pointer approach is optimal, Phantom Code provides instant guidance. It listens to your explanation and provides suggestions on whether you're using the right pattern for the problem. All supported through a chat interface, completely invisible to your interviewer. Plans start at ₹499/month at phantomcode.co.
Final Thoughts
Two pointers is a cornerstone technique. Master it and you'll solve problems faster, with better code, and more confidently. It's the kind of technique that once you see it, it becomes obvious—but only after practice.