When you see a problem that talks about pairs, sub‑arrays, or moving windows, the two‑pointer pattern is often the cleanest solution. The idea is simple: keep two indices (or node references) that move through the data structure at different speeds or in opposite directions. By adjusting them based on the condition you’re checking, you can explore all relevant combinations in a single linear pass.

Why Two Pointers Work

  • Linear scan – Instead of nested loops (O(n²)), you move the pointers together, achieving O(n).
  • Constant extra memory – You only store a few indices or node references, so space stays O(1).
  • Monotonic progress – Because the data is usually sorted or the condition is monotonic, moving a pointer forward never invalidates previous work.

Signals That a Problem Calls for Two Pointers

Cue in the statementTypical goalReason it fits the pattern
"Find a pair that sums to X"Pair searchSorted array lets you shrink/expand the window.
"Shortest subarray that contains …"Minimum windowExpanding right pointer, contracting left.
"Remove duplicates in‑place"In‑place modificationOne pointer reads, the other writes.
"Detect a cycle / find middle"Linked list traversalFast pointer moves twice as fast as slow.
"Rotate array k steps"Reversal techniqueThree reversals use start‑end pointers.

If you notice any of these phrases, pause and ask yourself whether you can maintain two moving markers that together capture the condition.

Worked Example: "Two Sum II – Input Array Is Sorted"

Problem: Given a sorted list of integers nums and a target t, return the indices of the two numbers that add up to t. Assume exactly one solution exists.

Approach: Place one pointer at the start (l) and one at the end (r). Compute s = nums[l] + nums[r]. If s equals t, you are done. If s is too small, move l right to increase the sum. If s is too large, move r left to decrease the sum. Repeat until the pair is found.

def two_sum_sorted(nums, target):
    l, r = 0, len(nums) - 1
    while l < r:
        cur = nums[l] + nums[r]
        if cur == target:
            return l, r
        if cur < target:
            l += 1
        else:
            r -= 1
    raise ValueError("No valid pair found")

Complexity: The loop moves each pointer at most n steps, so time is O(n). Only two integer variables are used, giving O(1) extra space.

Common pitfalls:

  • Forgetting to handle 0‑based vs 1‑based indexing when the platform expects 1‑based results.
  • Not checking for overflow when dealing with very large integers (in languages without big‑int support).
  • Assuming the array is sorted when the problem statement only says "non‑decreasing" – duplicates can affect the pointer movement logic.

Five Representative Practice Problems

1. Remove Duplicates from Sorted Array

Goal: Overwrite the array so each unique element appears once, and return the new length. Hint: Use a write pointer that lags behind a read pointer. When nums[read] != nums[write], advance write and copy the value.

2. Longest Substring Without Repeating Characters

Goal: Find the length of the longest window that contains no duplicate characters. Hint: Treat the left and right bounds as pointers. Expand the right pointer, and when a repeat appears, shrink the left pointer until the window is valid again. A hash map stores the last index of each character.

3. Container With Most Water

Goal: Given heights of vertical lines, pick two lines that together with the x‑axis form a container of maximal area. Hint: Start with pointers at both ends. Compute area, then move the pointer that points to the shorter line inward, because only a taller line could improve the area.

4. Minimum Window Substring

Goal: Find the smallest substring of s that contains all characters of t. Hint: Expand the right pointer until the window includes all required characters, then contract the left pointer to shrink the window while still satisfying the requirement. Track counts with a dictionary.

5. Find the Middle of a Linked List

Goal: Return the node at the midpoint of a singly linked list. Hint: Advance one pointer (slow) one step at a time and another (fast) two steps. When fast reaches the end, slow is at the middle.

Common Mistakes and How to Avoid Them

  1. Assuming monotonicity – Not all sorted‑array problems are monotonic. Verify that moving a pointer in one direction cannot make a previously false condition true again.
  2. Off‑by‑one errors – When the loop condition is while l < r, remember that l == r is the termination point; never access nums[l] after the loop.
  3. Ignoring edge cases – Empty inputs, single‑element arrays, or strings with all identical characters often break naïve pointer logic.
  4. Mixing pointer updates – Updating both pointers in the same iteration can skip viable combinations. Decide which pointer to move based on the current comparison.

How to Practice This

  1. Write the skeleton – For each problem, start by declaring two indices (l, r) and a while loop that checks l < r (or the appropriate condition).
  2. Simulate on paper – Walk through a small example step‑by‑step, updating pointers manually to see the invariant you are maintaining.
  3. Use Call Assistant – Record yourself explaining the approach aloud, then replay it to catch filler words or unclear logic. The assistant can keep the discussion focused on the pattern, helping you internalize the narrative.

FAQ

  • When should I prefer two pointers over a hash‑map? Use two pointers when the input is sorted or when you need to preserve order with O(1) extra space. Hash‑maps give O(n) time but O(n) space, which is unnecessary for many monotonic problems.

  • Can two pointers be used on unsorted data? Yes, but you typically need to sort first, which adds O(n log n) time. If the problem constraints allow sorting, the overall complexity may still be acceptable.

  • What if the array contains negative numbers? The two‑pointer technique still works as long as the array is sorted. The comparison logic (< or >) remains the same.

  • How do I adapt the pattern for linked lists? Replace array indices with node references. Move pointers by following next pointers, and be careful with None checks to avoid dereferencing null.

Frequently asked questions

When should I prefer two pointers over a hash‑map?

Use two pointers when the input is sorted or when you need O(1) extra space. Hash‑maps give O(n) time but require O(n) additional memory, which is unnecessary for many monotonic problems.

Can two pointers be used on unsorted data?

Yes, but you usually sort first, adding O(n log n) time. If the problem allows sorting within the time limits, the overall complexity may still be acceptable.

What if the array contains negative numbers?

The technique still works as long as the array is sorted. The comparison logic (`<` or `>`) does not change because the ordering is preserved.

How do I adapt the pattern for linked lists?

Replace array indices with node references and move pointers by following `next`. Ensure you check for `None` before accessing a node to avoid runtime errors.

#coding pattern#two pointers#algorithm#interview prep#python