DEV Community

Alex Hunter
Alex Hunter

Posted on • Originally published at leetcopilot.dev

Two Pointers Intuition for LeetCode Beginners: A Step-by-Step Visual Guide

Originally published on LeetCopilot Blog


Two pointers show up everywhere on LeetCode, but many beginners memorize patterns without understanding why they work. This guide builds your intuition with diagrams, examples, and a reusable decision process.

You've probably seen solutions that say "just use two pointers," as if that explains everything.

But when you face a new LeetCode problem, it’s not obvious:

  • Where should the pointers start?
  • When do they move together vs in opposite directions?
  • How do you know you’re not skipping valid answers?

This guide is a step-by-step, intuition-first walkthrough of the two pointers technique for LeetCode beginners, with diagrams, examples, and a simple mental checklist you can reuse.


TL;DR

  • Two pointers is about tracking a relationship between two positions in an array/string: moving them in a controlled way to avoid brute-force nested loops.
  • Common patterns: opposite ends (e.g., 2-sum on sorted arrays), sliding window (left/right in same direction), and fast/slow pointers (linked lists, cycles).
  • The core steps: identify the invariant you want to maintain, choose pointer starts, decide movement rules, and stop once all candidates are explored.
  • Beginners often move pointers without a clear invariant, forget about sorted requirements, or miss edge cases where pointers cross.
  • With a small set of templates, visual diagrams, and tools like AI-guided LeetCode practice, two pointers becomes a go-to technique, not a mysterious trick.

What Is the Two Pointers Technique, Really?

Intuition: Shrinking the search space

Naive approaches often check all pairs:

for i in [0..n-1]:
  for j in [i+1..n-1]:
    check(nums[i], nums[j])
Enter fullscreen mode Exit fullscreen mode

That’s O(n²) comparisons.

Two pointers replaces this with smart movement:

  • Instead of checking every pair, you maintain an invariant and move pointers in ways that eliminate many pairs at once.
  • You use structure (sorted order, or window constraints) to avoid rechecking work.

Visual: Opposite ends pattern

For a sorted array:

Index:  0   1   2   3   4
Nums:  [1,  2,  4,  7,  11]
        ^              ^
      left           right
Enter fullscreen mode Exit fullscreen mode

You:

  • Compute sum = nums[left] + nums[right].
  • Move left rightward or right leftward depending on whether sum is too small or too big.
  • Eliminate whole regions of pairs with one move.

Step-by-Step Framework: How to Design a Two-Pointer Solution

When you suspect two pointers might help, walk through this checklist.

Step 1: Look for structure in the data

Ask:

  • Is the array sorted or can it be sorted?
  • Are we dealing with a contiguous range (subarrays/substrings)?
  • Are we scanning a linked list?

Two pointers usually needs one of:

  • Sorted order (for opposite-ends pointers).
  • Contiguous segments (for sliding windows).
  • Next pointers (for fast/slow linked list pointers).

Step 2: Define the relationship between pointers

Typical relationships:

  • left < right and we consider the pair (left, right).
  • left and right define a window [left..right].
  • slow and fast move at different speeds along a list.

Write down the invariant:

  • For opposite ends: “All possible pairs are covered without duplicates.”
  • For sliding window: “Window [left..right] satisfies condition X.”
  • For fast/slow: “If there is a cycle, fast will eventually meet slow.”

Step 3: Decide movement rules

Ask for each step:

  • When do I move left?
  • When do I move right?
  • When do I stop?

This should preserve your invariant and eventually cover all meaningful candidates.


Example 1: Two Sum in a Sorted Array (Opposite Ends)

Problem: Given a sorted array and a target, return indices of two numbers that sum to target.

Why two pointers works here

  • Array is sorted → if the sum is too small, moving the left pointer right increases the sum; if too big, moving the right pointer left decreases it.
  • You can eliminate many pairs in one move instead of trying all O(n²) pairs.

Code example (TypeScript)

function twoSumSorted(nums: number[], target: number): number[] {
  let left = 0;
  let right = nums.length - 1;

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

    if (sum === target) {
      return [left, right];
    } else if (sum < target) {
      left++;          // need a larger sum
    } else {
      right--;         // need a smaller sum
    }
  }

  return [-1, -1]; // or throw error if guaranteed to exist
}
Enter fullscreen mode Exit fullscreen mode

Visual trace

nums = [1, 2, 4, 7, 11], target = 13

left=0 (1), right=4 (11), sum=12 < 13 → move left
left=1 (2), right=4 (11), sum=13 == 13 → answer
Enter fullscreen mode Exit fullscreen mode

Invariant:

At each step, if a pair exists, it lies within [left..right].


Example 2: Removing Duplicates from a Sorted Array (Same-Direction Pointers)

Problem: Given a sorted array, remove duplicates in-place and return the length of the deduplicated prefix.

Intuition

  • Use slow pointer to mark the end of the deduplicated prefix.
  • Use fast pointer to scan the array; when you find a new value, extend the prefix.

Visual

nums = [1, 1, 2, 2, 3]

slow points to last unique
fast scans next element
Enter fullscreen mode Exit fullscreen mode

Code sketch

function removeDuplicates(nums: number[]): number {
  if (nums.length === 0) return 0;

  let slow = 0;

  for (let fast = 1; fast < nums.length; fast++) {
    if (nums[fast] !== nums[slow]) {
      slow++;
      nums[slow] = nums[fast];
    }
  }

  return slow + 1;
}
Enter fullscreen mode Exit fullscreen mode

Invariant:

Elements in nums[0..slow] are unique and in sorted order.

This is the same conceptual idea as a sliding window—[0..slow] is the valid region you maintain.


Visual “Diagram” for Two Pointers

You can sketch a simple line:

Index:  0  1  2  3  4  5
Array: [a, b, c, d, e, f]
        ^           ^
      left        right
Enter fullscreen mode Exit fullscreen mode

Ask yourself:

  • What does the segment between them mean?
  • What happens to that meaning when I move one pointer?

Tools like LeetCopilot can support you by showing pointer movement step by step, especially when you’re debugging off-by-one behavior in your two-pointer loops.


Practical Preparation Strategies

Strategy 1: Group problems by two-pointer pattern

Instead of mixing everything, cluster:

  • Opposite-ends problems (sorted arrays, container with most water).
  • Same-direction / slow-fast problems (deduplication, partitioning).
  • Sliding window problems (overlap with two pointers).

In your DSA learning path, tag them accordingly.

Strategy 2: Write invariants as comments

Before coding:

# Invariant: subarray nums[left..right] has at most k distinct characters
# Invariant: if pair exists, it's within [left..right]
Enter fullscreen mode Exit fullscreen mode

This habit makes your pointer moves more deliberate and easier to debug.

Strategy 3: Practice dry runs on paper

Take a small array and trace:

  • How do left and right move?
  • Which indices are skipped?
  • Do you cover every relevant candidate pair or window?

Common Mistakes to Avoid

Mistake 1: Using two pointers without required structure

If the array isn’t sorted (and sorting breaks the problem), or if you’re not working with contiguous regions, two pointers may not be appropriate.

Mistake 2: Updating both pointers incorrectly

Randomly moving both pointers at once often loses candidates.

Fix: Move one pointer at a time based on a clear rule; think through what region you’re eliminating.

Mistake 3: Infinite loops or skipping termination

Always make sure each iteration progresses:

  • left increases or right decreases (opposite ends).
  • fast advances (fast/slow).
  • Eventually, the loop condition fails.

Mistake 4: Off-by-one in sliding windows

Window size is typically right - left + 1. Forgetting the +1 leads to subtle bugs.


FAQ

Q1: How do I know a problem wants two pointers?

Look for structure: sorted arrays, contiguous subarrays, or linked lists. Phrases like “find pair”, “min/max subarray”, “in-place without extra space” are strong hints.

Q2: What’s the difference between two pointers and sliding window?

Sliding window is a specific use of two pointers where left and right move in the same direction and define a window with some invariant. Opposite-ends two pointers is more about shrinking from both sides in a sorted structure.

Q3: Can I always sort the array to use two pointers?

Not always. If the problem cares about original indices or order, sorting may break the requirements. If you sort, be explicit about how you’ll map back to original positions.

Q4: How should I practice this pattern?

Pick 8–10 problems across different categories (pairs, windows, slow/fast). For each, write the invariant explicitly and try re-implementing from memory. Tools like AI-guided LeetCode practice can provide hint-only guidance to reinforce the pattern without spoiling it.

Q5: What about linked list cycle problems?

Fast/slow pointers (Floyd’s algorithm) is another two-pointer pattern: if a cycle exists, fast eventually meets slow. Practice it separately and note how the invariant there is about meeting points, not windows.


Conclusion

Two pointers is not a magical trick—it’s a way of exploiting structure to avoid brute-force pair checking.

The core mindset:

  • Identify the structure (sorted, window, list).
  • Define what your two pointers represent and what invariant they maintain.
  • Move them according to simple, predictable rules.

With enough clustered practice, two-pointer solutions will start to feel like natural first choices rather than clever afterthoughts.


If you're looking for an AI assistant to help you master LeetCode patterns and prepare for coding interviews, check out LeetCopilot.

Top comments (0)