← All concepts
Arrays & Two-Pointers

Arrays & Two-Pointers

Two indexes sweep an array from either end, or at two different speeds, so a nested loop collapses into a single pass.

Definition

The two-pointer technique keeps a pair of indexes moving through an array instead of comparing every element to every other element. In the classic converging form, one pointer starts at the front and the other at the back, and each step compares the values they point to before moving whichever pointer needs to change: on a sorted array, that turns an O(n squared) search for a pair into an O(n) walk that only ever moves inward. A second form, fast and slow pointers, moves both indexes from the same end but at different speeds, one step at a time versus two. That variant is what finds the middle of a sequence or detects a cycle: if a fast pointer ever laps a slow one, there is a loop. Both forms use only two extra variables, so the space cost stays O(1) no matter how large the array is, which is the main reason two pointers beat a hash-map based approach when the input is already sorted or when memory is tight.

Examples

function twoSumSorted(nums, target) {
  let left = 0, right = nums.length - 1;
  while (left < right) {
    const sum = nums[left] + nums[right];
    if (sum === target) return [left, right];
    if (sum < target) left++;
    else right--;
  }
  return [-1, -1];
}
twoSumSorted([1, 2, 5, 7, 9, 10], 9); // [1, 3]

left and right converge from opposite ends. A sum that is too small moves left forward to try a bigger value, a sum that is too big moves right backward to try a smaller one, and the pair is found in a single pass over the sorted array.

function reverseInPlace(arr) {
  let left = 0, right = arr.length - 1;
  while (left < right) {
    [arr[left], arr[right]] = [arr[right], arr[left]];
    left++;
    right--;
  }
  return arr;
}
reverseInPlace([1, 2, 3, 4]); // [4, 3, 2, 1]

Swapping the values at left and right and then stepping both pointers inward reverses the array without allocating a second array.

function hasCycle(head) {
  let slow = head, fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) return true;
  }
  return false;
}

Fast moves two steps for every one step of slow. If the list loops back on itself, fast eventually catches up to slow instead of running off the end.

Common mistakes

  • Running the converging form on an unsorted array; the pointer-movement rule only works because the array is sorted.
  • Using <= instead of < in the loop condition and reading one pointer past where the other already is.
  • Moving both pointers on every iteration when only one side needs to move, which can skip over the correct answer.

Key takeaways

  • Two pointers replace many nested loops, turning an O(n squared) scan into a single O(n) pass.
  • The converging form needs a sorted array and moves whichever pointer is on the side that is too small or too big.
  • The fast and slow form advances the two pointers at different speeds to find a middle element or detect a cycle.
  • Both forms use O(1) extra space, unlike an approach that builds an auxiliary set or map.
Check your understanding

In a sorted array, nums[left] + nums[right] comes out greater than the target. Which pointer should move, and why?

Where you see this

  • Finding a pair in a sorted array that sums to a target value without a nested loop.
  • Reversing an array or checking whether it is a palindrome in place, with no extra array.
  • Merging two already-sorted arrays by walking one pointer through each.
  • Detecting a cycle or finding the middle node of a linked list with fast and slow pointers.

Practice this

Further reading

Previous
JSON Serialization
Next
Hash Maps