DEV Community

Timevolt
Timevolt

Posted on

Two-Pointer Technique: The Matrix of Efficient Algorithms

The Quest Begins (The “Why”)

I still remember the first time I faced a “sorted array, find two numbers that add up to X” question in a mock interview. My brain went straight to the obvious solution: nest two loops, check every pair, and hope the test cases were tiny. The interviewer’s smile faded as my code choked on a modest input of 10 000 elements – O(n²) felt like trying to dodge Agent Smith’s bullets with a cardboard shield. I left the room frustrated, convinced I needed a secret move that interviewers only whispered about after hours.

That frustration sparked a quest: What if I could exploit the ordering of the array instead of ignoring it? The answer turned out to be simpler than I imagined, yet powerful enough to turn a sluggish brute force into a sleek, O(n) hero.

The Revelation (The Insight)

The two‑pointer technique isn’t magic; it’s a direct consequence of how sorted data behaves. Picture two agents standing at opposite ends of a hallway (the array). If the sum of the numbers they’re holding is too small, the only way to increase it is to move the left agent forward – because every element to the right is larger or equal. If the sum is too large, we move the right agent backward to shrink it.

Because the array is sorted, each move guarantees we’re not missing any viable pair: we’re either discarding values that can’t possibly work or stepping closer to the answer. The pointers never need to retreat, so each element is inspected at most once → linear time, constant extra space.

In short: sorted + monotonic movement = guaranteed coverage without re‑checking.

Wielding the Power (Code & Examples)

Problem 1 – Two Sum II (LeetCode 167)

Given a 1‑indexed, sorted array numbers, return indices of the two numbers that add up to target.

The Struggle (Brute Force)

def two_sum_brute(numbers, target):
    n = len(numbers)
    for i in range(n):
        for j in range(i + 1, n):
            if numbers[i] + numbers[j] == target:
                return [i + 1, j + 1]   # 1‑based
    return []
Enter fullscreen mode Exit fullscreen mode

O(n²) time, O(1) space – passes tiny cases but times out on anything realistic.

The Victory (Two‑Pointer)

def two_sum(numbers, target):
    left, right = 0, len(numbers) - 1
    while left < right:
        cur = numbers[left] + numbers[right]
        if cur == target:
            return [left + 1, right + 1]   # 1‑based indices
        if cur < target:
            left += 1          # need a larger sum
        else:
            right -= 1         # need a smaller sum
    return []
Enter fullscreen mode Exit fullscreen mode

Why it works: At each step we know the exact direction that can improve the sum because of sorting. No pair is skipped, and each index moves at most once → O(n) time, O(1) space.

Common Trap

Forgetting that the input must be sorted. If you apply this to an unsorted array, the monotonic guarantee vanishes and you’ll miss solutions. Either sort first (O(n log n)) or confirm the precondition.

Problem 2 – Remove Duplicates from Sorted Array (LeetCode 26)

Modify the array in‑place so that each element appears only once and return the new length.

The Struggle (Extra Space)

def remove_dups_extra(nums):
    seen = set()
    write = 0
    for x in nums:
        if x not in seen:
            seen.add(x)
            nums[write] = x
            write += 1
    return write
Enter fullscreen mode Exit fullscreen mode

O(n) time but O(n) extra space – not optimal for the interview’s “in‑place” ask.

The Victory (Two‑Pointer)

def remove_duplicates(nums):
    if not nums:
        return 0
    write = 1                     # position to write next unique
    for read in range(1, len(nums)):
        if nums[read] != nums[read - 1]:
            nums[write] = nums[read]
            write += 1
    return write
Enter fullscreen mode Exit fullscreen mode

Why it works: The read pointer scans every element; the write pointer lags behind, copying only when we encounter a new value (different from the previous one). Because the array is sorted, duplicates are contiguous, so a single pass removes them all. Again, O(n) time, O(1) space.

Common Trap

Moving both pointers forward on a match (if nums[read] == nums[read-1]: read += 1) without copying the unique value leads to lost data or an incorrect length. Keep the copy step inside the if block that detects a change.

Why This New Power Matters

Mastering the two‑pointer pattern is like unlocking a universal key for a whole class of problems:

  • Pair‑sum variants (closest sum, count pairs less than K, etc.)
  • Sliding‑window challenges (longest substring with at most two distinct chars, minimum size subarray sum)
  • In‑place transformations (remove element, sort colors, squaring a sorted array)

All share the same core insight: when data exhibits monotonic order, you can chase a condition from both ends and let the pointers do the heavy lifting. The payoff? Linear runtime, constant auxiliary space, and code that reads like a story rather than a tangled nest of loops.

When I finally nailed this technique, my interview performance jumped from “barely scraping by” to “consistently advancing to the next round.” It felt like swapping a rusty bike for a turbocharged hoverboard—suddenly the obstacles that once seemed insurmountable glided beneath me.

Your Turn

Pick one of the classic two‑pointer problems below and implement it using the pattern we just explored. Try to resist the urge to fall back to nested loops; let the sorted nature guide your pointer moves.

  • Container With Most Water (LeetCode 11) – maximize area between two lines.
  • 3Sum (LeetCode 15) – find all unique triplets that sum to zero.
  • Sort Colors (LeetCode 75) – Dutch‑national‑flag problem.

Post your solution, share a insight you discovered while coding, and let’s keep the quest going. Happy pointer‑chasing! 🚀

Top comments (0)