Two Pointer pattern in simple words
Many array and string problems look like they need two loops. Often, they do not. If you use two pointers, you can solve them with just one pass. Here is how the pattern works, explained in plain words.
What is the Two Pointer pattern?
It is a technique where two variables, called pointers, move through a data structure at the same time. The data structure can be an array, a string or a linked list. The two pointers usually start from different positions.
A pointer is nothing special here. For an array or a string, it is just an index number like left = 0. For a linked list, it is a reference to a node.
Three ways the pointers can move
There are only three movements to remember.
1. Towards each other
One pointer starts at the left end, the other at the right end. They move closer until they meet. This is the most common type.
2. Away from each other
Both pointers start in the middle and move outwards, one to each side. This is useful when you want to grow something from a centre, like finding the longest palindrome around a point.
3. Same direction (slow and fast)
Both pointers move the same way, but at different speeds or with different jobs. One is called slow and the other fast. This is used for removing duplicates, partitioning, and linked list problems.
Where is it used?
The pattern helps when a problem is about comparing, searching or partitioning elements. If you can say "I need to look at two positions at once", think of two pointers.
Why use two pointers?
- No unnecessary loops. One pass is often enough instead of two nested loops.
- Early stop. You can stop as soon as a condition fails or a mismatch is found.
- Works in place. You do not need a new array or a copy of the data.
- Compares both ends. Good for palindromes and pair sums.
- Better time and space. Usually
O(n)time andO(1)extra space. - Finds pairs with a given property.
- Removes or merges elements. For example, removing duplicates or merging two sorted arrays.
- Detects loops and middle points. For example, a cycle in a linked list or the middle node.
Template 1: one input, pointers from both ends
Use this when you have one array or string and the two pointers move towards each other.
a = [1, 4, 3, 2, 9]
left = 0
right = a.length - 1
while left < right:
if CONDITION:
left = left + 1
else:
right = right - 1
return ans
The loop runs while left < right. In each step, the condition tells you which pointer to move. Every step moves one pointer, so the loop can run at most n times.
Example: pair sum in a sorted array
Find two numbers that add up to a target. The array is sorted: [1, 2, 4, 7, 11, 15] and the target is 15.
left = 0
right = a.length - 1
while left < right:
sum = a[left] + a[right]
if sum == target:
return [left, right]
if sum < target:
left = left + 1 # need a bigger sum
else:
right = right - 1 # need a smaller sum
return not found
| Step | left | right | Sum | What we do |
|---|---|---|---|---|
| 1 | 1 | 15 | 16 | Too big, move right back |
| 2 | 1 | 11 | 12 | Too small, move left forward |
| 3 | 2 | 11 | 13 | Too small, move left forward |
| 4 | 4 | 11 | 15 | Match. Done. |
Because the array is sorted, we know exactly which pointer to move. We never check a pair twice. A normal nested loop would check every pair, which is O(n²). Two pointers does it in O(n).
Template 2: two inputs, go until both are finished
Use this when you have two arrays (or two strings). You keep one pointer on each, and you move forward until both are fully used.
a = [1, 4, 7]
b = [2, 3, 9, 10]
i = 0
j = 0
while i < a.length AND j < b.length:
if CONDITION: # e.g. a[i] <= b[j]
use a[i]
i = i + 1
else:
use b[j]
j = j + 1
# one array may still have items left
while i < a.length:
use a[i]
i = i + 1
while j < b.length:
use b[j]
j = j + 1
The first loop stops when one array runs out. The two small loops after it take care of whatever is left in the other. This is how you merge two sorted arrays: at each step, pick the smaller value and move that pointer. The result is [1, 2, 3, 4, 7, 9, 10].
Template 3: same direction, slow and fast
Here both pointers start at the beginning (or at the head of a list). Fast goes ahead and looks at each element. Slow stays behind and marks where the next good value should be placed.
Example: remove duplicates from a sorted array
slow = 0
for fast = 1 to a.length - 1:
if a[fast] != a[slow]:
slow = slow + 1
a[slow] = a[fast]
return slow + 1 # length of the array without duplicates
For [1, 1, 2, 2, 3, 3], the array becomes [1, 2, 3, ...] and the answer is 3. We did it in place, with no extra array.
Example: linked list
For linked lists, slow moves one node at a time and fast moves two nodes at a time.
- Middle node: when fast reaches the end, slow is at the middle.
- Cycle detection: if there is a loop, fast will catch up with slow from behind. If fast reaches the end instead, there is no loop.
slow = head
fast = head
while fast != null AND fast.next != null:
slow = slow.next
fast = fast.next.next
if slow == fast:
return true # cycle found
return false
How to know when to use it
Ask yourself these questions:
- Is the data an array, a string or a linked list?
- Do I need to compare or combine two positions?
- Is the data sorted, or can I sort it?
- Am I about to write two nested loops over the same data?
If the answer is yes to most of these, try two pointers.
Practice problems
These are good to try, in this order:
- Valid Palindrome
- Two Sum II (sorted input array)
- Remove Duplicates from Sorted Array
- Merge Sorted Array
- Container With Most Water
- Middle of the Linked List
- Linked List Cycle
Summary
Two pointers is a simple idea: use two positions instead of one, and let them help each other. Move them towards each other, away from each other, or in the same direction. Most of the time, you turn a slow O(n²) solution into a fast O(n) one, and you do not need any extra memory.
Pick one problem from the list above and solve it today. After two or three, you will start to spot the pattern on your own.