DEV Community

Timevolt
Timevolt

Posted on

The Two-Pointer Technique: My 'Matrix' Moment for O(n) Bliss

The Quest Begins (The "Why")

I still remember the first time I faced a classic interview problem: “Given a sorted array, find two numbers that add up to a target.” My brain went straight to the brute‑force double loop—O(n²) and a sinking feeling that I was about to get stuck in an infinite loading screen. I spent an hour staring at the screen, thinking there had to be a smarter way, but every idea felt like trying to dodge bullets in slow motion.

Then a colleague slipped a hint over coffee: “What if you start from both ends and walk toward the middle?” That tiny nudge felt like Neo seeing the code of the Matrix for the first time—everything clicked, and the problem turned from a nightmare into a dance. I realized the two‑pointer technique isn’t just a trick; it’s a mindset shift that turns many seemingly tough array/string questions into linear‑time victories.

The Revelation (The Insight)

So why does moving two pointers from opposite ends work?

Think of a sorted array as a number line. If the sum of the two current values is too small, you need a larger number to reach the target. The only way to get a larger number while preserving order is to move the left pointer rightward—because everything to its left is even smaller. Conversely, if the sum is too big, you need a smaller number, so you move the right pointer leftward.

Each step discards a portion of the search space that can never contain a valid pair, because the array’s ordering guarantees monotonicity. You never miss a solution, and you never revisit the same pair. In the worst case each element is looked at once → O(n) time, O(1) extra space.

The beauty is that this logic generalizes: any problem where you need to find a pair (or triplet) that satisfies a monotonic condition (sum, difference, product) on a sorted sequence can be attacked the same way.

Wielding the Power (Code & Examples)

Problem 1 – Classic Two Sum (Sorted)

Naïve attempt (O(n²))

function twoSumBrute(arr, target) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] + arr[j] === target) return [i, j];
    }
  }
  return [];
}
Enter fullscreen mode Exit fullscreen mode

Why it hurts: For every i we scan the rest of the array—quadratic work, and it feels like rewatching the same scene over and over.

Two‑pointer triumph (O(n))

function twoSumSorted(arr, target) {
  let left = 0;
  let right = arr.length - 1;

  while (left < right) {
    const sum = arr[left] + arr[right];

    if (sum === target) return [left, right];
    if (sum < target) left++;   // need a bigger sum → move left forward
    else right--;               // sum too big → move right backward
  }
  return []; // no pair found
}
Enter fullscreen mode Exit fullscreen mode

What changed: We let the sorted order do the heavy lifting. Each iteration discards either all elements left of left or right of right that could never work. No nested loops, no extra memory—just two indices marching toward each other.

Common trap – forgetting the < condition and letting left cross right. If you use <= you might check the same element twice and, more importantly, you could miss a valid pair when the array length is odd. Keep the loop strict: while (left < right).

Problem 2 – Container With Most Water (LeetCode 11)

Here the goal is to maximize min(height[left], height[right]) * (right - left). The same monotonic intuition applies: the area is limited by the shorter line. If we move the pointer at the shorter line, we might find a taller line and increase the possible height; moving the taller line can only keep or reduce the height while shrinking the width—never a win.

function maxArea(height) {
  let left = 0;
  let right = height.length - 1;
  let max = 0;

  while (left < right) {
    const h = Math.min(height[left], height[right]);
    const width = right - left;
    max = Math.max(max, h * width);

    if (height[left] < height[right]) {
      left++;   // bet on a taller left line
    } else {
      right--;  // bet on a taller right line
    }
  }
  return max;
}
Enter fullscreen mode Exit fullscreen mode

Why it works: Each step discards the line that cannot possibly lead to a larger area because any container using that line would be bounded by its current height (the limiting factor) and a smaller width.

Typical mistake – moving both pointers when the heights are equal. If you shift both, you might skip a configuration where the other line could pair with a taller counterpart later. Move only one pointer (either side) when heights match; the algorithm above does that implicitly by picking the else branch.

Why This New Power Matters

Mastering the two‑pointer technique is like gaining a shortcut in a sprawling open‑world game: you can sprint past grind‑heavy sections and head straight for the boss. Suddenly, interview questions that once felt like puzzles wrapped in fog become straightforward walks through a sunlit forest.

You’ll start spotting patterns everywhere—3‑sum, sorting squares, palindrome checks, removing duplicates, even certain linked‑list problems (fast/slow pointers). The mental model stays the same: order lets you eliminate impossible choices with a single step.

And the best part? The code stays tiny, readable, and bug‑resistant when you respect the loop condition and pointer‑movement rule. No more nested loops drowning in off‑by‑one errors; just two indices, a clear condition, and a satisfying O(n) guarantee.

Your Turn

Grab a piece of paper (or your favorite IDE) and try this:

Challenge: Given a sorted array of integers, return the number of unique pairs whose sum is less than a given value K. Solve it in O(n) time with O(1) extra space.

Post your solution in the comments, or tweet it with #TwoPointerQuest. I can’t wait to see how you wield this newfound power!


Happy coding, and may your pointers always find the sweet spot.

Top comments (0)