DAY 4 / 30 · WEEK 1

Two-Pointer Pattern

Goal of the day

Concept

Picture two librarians searching a sorted shelf for two books whose page counts add up to exactly 500. Instead of checking every possible pair (that's the brute-force O(n²) way), one librarian starts at the thinnest book, the other at the thickest, and they walk toward each other. If the two page counts add up to too much, the one at the thick end steps in (a smaller book). If it's too little, the one at the thin end steps in (a bigger book). Every step rules out one book from further consideration — that's what makes it O(n) instead of O(n²). This only works because the shelf is sorted; on a shuffled shelf, moving a pointer wouldn't reliably move the sum in a useful direction.

The second shape is different: two pointers moving the same direction, one fast (scanning ahead) and one slow (marking where to write next). This is how you compact an array in place — for example, squeezing out duplicates from a sorted array without allocating a new one. You saw this exact "read pointer / write pointer" idea already in Day 3's String Compression problem — two-pointer isn't one trick, it's a family of tricks that all avoid a nested loop.

Diagrams

1. Converging pointers (opposite ends)

Sorted array, target sum = 12. Pointers start at both ends. 2 4 5 7 9 left right 2 + 9 = 11, too small → move left pointer inward (need a bigger number). If the sum were too big, we'd move right inward instead (need a smaller number). Each move rules out one element for good — that's the whole O(n) budget, spent once each.

2. Same-direction pointers (read / write)

Remove duplicates from a sorted array, in place: [1, 1, 2, 2, 3] 1 1 2 2 3 write (slow) — index 1 read (fast) — index 4 read scans every element; write only advances (and copies) when read finds a value different from what's already written. Duplicates get overwritten. Result after the scan: [1, 2, 3, ...] — the first (write+1) slots are the answer.

Interactive visualizations

left / write pointer right / read pointer found / kept ruled out / duplicate

1. Two Sum II — converging pointers

Press Play or Step to begin.

2. Remove Duplicates — read/write pointers

Press Play or Step to begin.

The code

// --- Shape 1: converging pointers (array must be SORTED) ---
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++;   // need a bigger sum → drop the smaller number
    else right--;               // need a smaller sum → drop the bigger number
  }
  return [-1, -1]; // no pair found
}

// --- Shape 2: same-direction read/write pointers ---
function removeDuplicates(nums) {
  let write = 0; // boundary of the unique section built so far; nums[0] is already "written"
  for (let read = 1; read < nums.length; read++) {
    if (nums[read] !== nums[write]) {
      write++;
      nums[write] = nums[read];
    }
  }
  return write + 1; // length of the deduplicated section
}

Line by line:

Dry run: twoSumSorted([2, 4, 5, 7, 9], target = 12)

Stepleftrightnums[left] + nums[right]Action
10 (2)4 (9)11too small → left++
21 (4)4 (9)13too big → right--
31 (4)3 (7)11too small → left++
42 (5)3 (7)12match! return [2, 3]

Found in 4 steps instead of checking all 5×4/2 = 10 possible pairs — and this same array/target pair is what the interactive visualization above animates.

Dry run: removeDuplicates([1, 1, 2, 2, 3])

readnums[read]nums[write]Actionwrite after
111 (write=0)same → duplicate, skip0
221 (write=0)different → write++, copy1
322 (write=1)same → duplicate, skip1
432 (write=1)different → write++, copy2

Returns write + 1 = 3 — the first 3 slots, [1, 2, 3], are the deduplicated result. This matches Diagram 2 above at the moment read=4: write is still at index 1, about to become 2.

Complexity

ApproachTimeSpaceWhy
Brute-force pair check (nested loop)O(n²)O(1)Every element compared against every other element.
Two-pointer, convergingO(n)O(1)Each pointer only ever moves inward — at most n moves total, combined.
Two-pointer, read/writeO(n)O(1)A single pass; write only touches each output slot once.

Interview corner

How to talk through it out loud: state the precondition first — "since this array is sorted, I can use two pointers converging from both ends instead of checking every pair" — then narrate each pointer move in terms of the goal: "the sum's too small, so I need a bigger number, so I'll move left forward." Naming why a pointer moves, not just that it moves, is what shows you understand the pattern instead of having memorized it.

Practice problems

1. Remove Duplicates from Sorted Array (Easy — LeetCode: Remove Duplicates from Sorted Array)

Remove duplicates from a sorted array in place so each unique element appears once, and return the count of unique elements.

2. Two Sum II - Input Array Is Sorted (Medium — LeetCode: Two Sum II - Input Array Is Sorted)

Given a sorted array and a target, return the 1-indexed positions of the two numbers that add up to the target.

3. Container With Most Water (Medium — LeetCode: Container With Most Water)

Given heights of vertical lines, find two lines that, together with the x-axis, form a container holding the most water.

Quiz

1. The converging two-pointer pattern for pair-sum problems requires the array to be:

2. In the converging pattern, if the current sum is LESS than the target, which pointer moves?

3. In the read/write pointer pattern for removing duplicates, what does the write pointer represent?

4. What's the time complexity of the two-pointer approach, versus a brute-force nested-loop pair check?

5. In Container With Most Water, which pointer should you move inward?