Know this before you start: “Same direction: slow and fast pointers”
Two pointers moving the same way
A fast pointer reads every item; a slow pointer marks where the next kept item goes. Rewrites arrays in place in one pass.
The idea
Both pointers start at the left. FAST walks every position; SLOW only moves when FAST finds something worth keeping, and that item is copied to SLOW's spot.
Everything before SLOW is the finished answer, so you never need a second array. This is how “remove duplicates”, “move zeroes” and “remove element” are solved in place.
The same idea with different speeds (one step vs two) finds the middle of a linked list or detects cycles.
See it step by step
Move zeroes to the end of [0, 1, 0, 3, 12]
- fast = 0 (0): skip. fast = 1 (1): write at slow = 0 → [1, 1, 0, 3, 12], slow = 1
- fast = 2 (0): skip. fast = 3 (3): write at 1 → [1, 3, 0, 3, 12], slow = 2
- fast = 4 (12): write at 2 → [1, 3, 12, 3, 12], slow = 3
- Fill positions 3… with 0 → [1, 3, 12, 0, 0].
In code (Python)
def move_zeroes(nums):
slow = 0
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow] = nums[fast]
slow += 1
for i in range(slow, len(nums)):
nums[i] = 0
return nums
def remove_duplicates(sorted_nums):
slow = 1
for fast in range(1, len(sorted_nums)):
if sorted_nums[fast] != sorted_nums[slow - 1]:
sorted_nums[slow] = sorted_nums[fast]
slow += 1
return slow # length of the de-duplicated prefix
print(move_zeroes([0, 1, 0, 3, 12])) # [1, 3, 12, 0, 0]
print(remove_duplicates([1, 1, 2, 3, 3])) # 3O(n) time, O(1) space.
When you'll use it
- “In place”, “without extra space”, removing or compacting items, partitioning.
- Linked lists: middle node, cycle detection (fast moves two steps).
Common mistakes
- Starting slow and fast at different places than the logic expects.
- Forgetting the elements after SLOW still hold old values.