Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Two Pointers
DSA

Two Pointers

Understand how two pointers reduce unnecessary work in sorted arrays, pairs, subarrays, and sequence problems.

Two Pointers uses two indices moving through data together to turn brute force O(n²) into O(n).

Its core idea:

Instead of checking all pairs, move intelligently based on a condition.

Focus on recognizing:

Sorted data + pair/search problem → Two Pointers


Core Template

public int twoPointers(int[] arr) {
    int left = 0;
    int right = arr.length - 1;

    while (left < right) {
        if (conditionMet(arr[left], arr[right])) {
            // process answer
        }

        if (shouldMoveLeft()) {
            left++;
        } else {
            right--;
        }
    }

    return result;
}
def two_pointers(arr):
    left, right = 0, len(arr) - 1

    while left < right:
        if condition_met(arr[left], arr[right]):
            pass  # process answer

        if should_move_left():
            left += 1
        else:
            right -= 1

    return result
int twoPointers(vector<int>& arr) {
    int left = 0;
    int right = (int)arr.size() - 1;

    while (left < right) {
        if (conditionMet(arr[left], arr[right])) {
            // process answer
        }

        if (shouldMoveLeft()) left++;
        else right--;
    }

    return result;
}
function twoPointers(arr) {
  let left = 0,
    right = arr.length - 1;

  while (left < right) {
    if (conditionMet(arr[left], arr[right])) {
      // process answer
    }

    if (shouldMoveLeft()) left++;
    else right--;
  }

  return result;
}

Movement is the algorithm: decide → move one pointer → never backtrack.



Pattern 1: Opposite Ends (Pair Sum in Sorted Array)

Watch opposite-end pointers converge on [2,7,11,15] with target 9 — each comparison eliminates one candidate. Press to animate.

Two Pointers (Opposite Ends & Same Direction)

Two pointers replace nested loops. Opposite-ends pointers solve sorted-pair problems in O(n) by eliminating a whole end each step; same-direction pointers dedupe or partition in place with a write/read split.

Sorted [2,7,11,15], target 9. 2+15=17 > 9 → 15 can never pair with anything (it's the biggest) → drop it. 2+11=13 > 9 → drop 11. 2+7=9 ✓ found. Watch how each probe kills one entire end; 3 probes instead of 6 pairs.

ARRAY VISUALIZER
Steps
2
0
7
1
11
2
15
3
Press ▶ to animate, or step through manually.
Variables
keys: ← → space F
Pseudocode

                        1
                        left = 0, right = n - 1
                      
                        2
                        while left < right:
                      
                        3
                          sum = arr[left] + arr[right]
                      
                        4
                          if sum == target: return pair
                      
                        5
                          if sum < target: left++    // need bigger
                      
                        6
                          else:            right--   // need smaller
                      
                        7
                        return none
                      
public boolean twoSumSorted(int[] arr, int target) {
    int left = 0, right = arr.length - 1;

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

        if (sum == target) return true;

        if (sum < target) {
            left++;
        } else {
            right--;
        }
    }

    return false;
}
def two_sum_sorted(arr, target):
    left, right = 0, len(arr) - 1

    while left < right:
        s = arr[left] + arr[right]

        if s == target:
            return True

        if s < target:
            left += 1
        else:
            right -= 1

    return False
bool twoSumSorted(vector<int>& arr, int target) {
    int left = 0, right = (int)arr.size() - 1;

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

        if (sum == target) return true;

        if (sum < target) left++;
        else right--;
    }

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

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

    if (sum === target) return true;

    if (sum < target) left++;
    else right--;
  }

  return false;
}

Sorted order lets each comparison eliminate a whole row or column of the pair matrix.


Pattern 2: Fast & Slow (Remove Duplicates)

slow owns the unique prefix; fast explores. A new value gets copied down.

Remove Duplicates (Fast & Slow Pointers)

Deduplicate a sorted array in place. A slow pointer guards the end of the unique prefix; a fast pointer scans ahead and, only when it finds a new value, copies it past the slow pointer. O(n) time, O(1) extra space.

Array [1,1,2,2,3]. slow guards the unique zone [1]; fast scans. Duplicates (1, then 2) are skipped; new values (2, then 3) make slow++ and get written down. Watch the array change as 2 and 3 are pulled left: [1,2,3,2,3]. The first slow+1 slots (3) are the answer; the tail is leftover garbage and that's fine.

ARRAY VISUALIZER
Steps
1
0
1
1
2
2
2
3
3
4
Press ▶ to animate, or step through manually.
Variables
keys: ← → space F
Pseudocode

                        1
                        slow = 0
                      
                        2
                        for fast in 1..n-1:
                      
                        3
                          if nums[fast] != nums[slow]:
                      
                        4
                            slow++
                      
                        5
                            nums[slow] = nums[fast]   // extend unique prefix
                      
public int removeDuplicates(int[] nums) {
    if (nums.length == 0) return 0;

    int slow = 0;

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

    return slow + 1;
}
def remove_duplicates(nums):
    if not nums:
        return 0

    slow = 0

    for fast in range(1, len(nums)):
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]

    return slow + 1
int removeDuplicates(vector<int>& nums) {
    if (nums.empty()) return 0;

    int slow = 0;

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

    return slow + 1;
}
function removeDuplicates(nums) {
  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;
}

Fast reads, slow writes — one pass filters the array in place.


Pattern 3: Partitioning (Dutch National Flag)

Three pointers sort 0s, 1s and 2s in a single pass — watch each swap lock a region.

Dutch National Flag (One Pass vs Counting)

Sort an array containing only 0, 1, and 2. The elegant one-pass solution is Dutch National Flag (three pointers); the easy alternative is counting sort (tally then overwrite in two passes).

Array [2,0,2,1,1,0]. low=mid=0, high=5. mid sees 2 → swap with high (2↔0) and high--, but DO NOT advance mid (the swapped-in value is unexamined). 0 → swap into the 0-zone and advance both. 1 → just mid++. Watch the three zones meet: [0,0,1,1,1,2] in one pass. The key trap is rechecking mid after a 2-swap.

ARRAY VISUALIZER
Steps
2
0
0
1
2
2
1
3
1
4
0
5
Press ▶ to animate, or step through manually.
Variables
keys: ← → space F
Pseudocode

                        1
                        low = 0, mid = 0, high = n - 1
                      
                        2
                        while mid <= high:
                      
                        3
                          if nums[mid] == 0:
                      
                        4
                            swap(low, mid); low++; mid++   // 0 → front
                      
                        5
                          elif nums[mid] == 1:
                      
                        6
                            mid++                          // 1 stays middle
                      
                        7
                          else:
                      
                        8
                            swap(mid, high); high--        // 2 → back; recheck mid!
                      

Three pointers (low, mid, high) classify elements into regions — covered fully in Partition.

Same-direction pointers that maintain invariant regions, not pairs.


Pattern 4: Merge Two Sorted Arrays

Compare the heads, take the smaller, advance that pointer — linear merge.

Merge Two Sorted Arrays

Merge two sorted arrays into one sorted output using two pointers (i on A, j on B) that always compare the two current heads and take the smaller. One pass, O(n+m). This is the inner loop of merge sort.

A=[1,3,5], B=[2,4,6] laid out side by side. Compare heads: take 1 (A), then 2 (B), 3 (A), 4 (B), then B is exhausted so A's 5 (and 6) drop in. The 'out' state chip shows the merged prefix growing: [1,2,3,4,5,6]. The a[i]/b[j] pointers mark the next candidate from each side.

ARRAY VISUALIZER
Steps
1
0
3
1
5
2
2
3
4
4
6
5
Press ▶ to animate, or step through manually.
Variables
keys: ← → space F
Pseudocode

                        1
                        i = j = k = 0
                      
                        2
                        while both arrays have items:
                      
                        3
                          if a[i] <= b[j]: out[k++] = a[i++]
                      
                        4
                          else:            out[k++] = b[j++]
                      
                        5
                        copy leftovers
                      
public int[] mergeSorted(int[] a, int[] b) {
    int i = 0, j = 0, k = 0;
    int[] res = new int[a.length + b.length];

    while (i < a.length && j < b.length) {
        if (a[i] < b[j]) {
            res[k++] = a[i++];
        } else {
            res[k++] = b[j++];
        }
    }

    while (i < a.length) res[k++] = a[i++];
    while (j < b.length) res[k++] = b[j++];

    return res;
}
def merge_sorted(a, b):
    i = j = 0
    res = []

    while i < len(a) and j < len(b):
        if a[i] < b[j]:
            res.append(a[i])
            i += 1
        else:
            res.append(b[j])
            j += 1

    res.extend(a[i:])
    res.extend(b[j:])
    return res
vector<int> mergeSorted(vector<int>& a, vector<int>& b) {
    vector<int> res;
    int i = 0, j = 0;

    while (i < (int)a.size() && j < (int)b.size())
        res.push_back(a[i] < b[j] ? a[i++] : b[j++]);

    while (i < (int)a.size()) res.push_back(a[i++]);
    while (j < (int)b.size()) res.push_back(b[j++]);

    return res;
}
function mergeSorted(a, b) {
  const res = [];
  let i = 0,
    j = 0;

  while (i < a.length && j < b.length)
    res.push(a[i] < b[j] ? a[i++] : b[j++]);

  while (i < a.length) res.push(a[i++]);
  while (j < b.length) res.push(b[j++]);

  return res;
}

Two independent pointers, each owning one sorted input.


Pattern 5: Palindrome Check

Mirrored pairs compare and vanish; crossing in the middle certifies a palindrome.

Valid Palindrome (Opposite Ends)

Check a string is a palindrome by comparing the two ends and walking inward. The first mismatch returns false; if the pointers cross, every mirrored pair matched.

String 'racecar'. L and R start at the ends: r=r ✓, a=a ✓, c=c ✓. They cross at the middle 'e', which never needs a partner → palindrome. A single mismatch (e.g. 'racecat') would return false at that one comparison. O(n) time, O(1) space.

ARRAY VISUALIZER
Steps
r
0
a
1
c
2
e
3
c
4
a
5
r
6
Press ▶ to animate, or step through manually.
Variables
keys: ← → space F
Pseudocode

                        1
                        left = 0, right = n-1
                      
                        2
                        while left < right:
                      
                        3
                          if s[left] != s[right]: return false
                      
                        4
                          left++, right--
                      
                        5
                        return true
                      
public boolean isPalindrome(String s) {
    int left = 0, right = s.length() - 1;

    while (left < right) {
        while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
        while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;

        if (Character.toLowerCase(s.charAt(left)) !=
            Character.toLowerCase(s.charAt(right))) {
            return false;
        }

        left++;
        right--;
    }

    return true;
}
def is_palindrome(s):
    left, right = 0, len(s) - 1

    while left < right:
        while left < right and not s[left].isalnum():
            left += 1
        while left < right and not s[right].isalnum():
            right -= 1

        if s[left].lower() != s[right].lower():
            return False

        left += 1
        right -= 1

    return True
bool isPalindrome(string s) {
    int left = 0, right = (int)s.size() - 1;

    while (left < right) {
        while (left < right && !isalnum(s[left])) left++;
        while (left < right && !isalnum(s[right])) right--;

        if (tolower(s[left]) != tolower(s[right])) return false;

        left++;
        right--;
    }

    return true;
}
function isPalindrome(s) {
  const t = s.toLowerCase();
  const alnum = (c) => /[a-z0-9]/.test(c);
  let left = 0,
    right = t.length - 1;

  while (left < right) {
    while (left < right && !alnum(t[left])) left++;
    while (left < right && !alnum(t[right])) right--;

    if (t[left] !== t[right]) return false;

    left++;
    right--;
  }

  return true;
}

Shrink from both ends, skipping anything irrelevant along the way.


Common Mistakes

Using opposite ends on unsorted data.

The “move which pointer?” decision depends on ordering. Sort first or use hashing instead.


Moving both pointers blindly.

Exactly one pointer moves per step, chosen by the condition — otherwise you skip answers.


Wrong loop bound.

left < right for pairs; left <= right when the middle element matters.


Complexity

PatternTime
Opposite endsO(n)
Fast & slowO(n)
MergeO(n + m)

My Private Notes

Notes are auto-saved locally to this device.