Two Pointers (Same Direction)

Published: (January 9, 2026 at 05:18 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Overview

The Two Pointers (Same Direction) pattern uses two indices moving forward together through a data structure (usually an array or string).
Unlike opposite‑direction pointers, both pointers only move left → right, but at different speeds or based on conditions.
This pattern is extremely common in linear‑time optimizations.

When to Use Two Pointers (Same Direction)

Use this pattern when:

  • You process elements in order.
  • You want to compare or relate two positions.
  • You need to skip, filter, or track a range.
  • You want to avoid nested loops (O(N²)).

Typical Problem Statements

  • Remove duplicates from a sorted array
  • Check if an array is a subsequence of another
  • Longest substring without repeating characters
  • Merge two sorted arrays
  • Partition an array based on a condition
  • Fast & slow pointer problems (variant)

Core Intuition

Think of the pointers as:

  • Reader & Writer
  • Slow & Fast
  • Anchor & Explorer

One pointer represents a reference position, the other scans ahead.

General Algorithm Template

slow = 0
for fast in range(0, n):
    if condition_satisfied:
        # perform operation using slow & fast
        slow += 1
  • fast moves every iteration.
  • slow moves only when needed.

Key Properties

  • Time Complexity: O(N)
  • Space Complexity: O(1)
  • Works best on sorted arrays or strings.
  • Avoids unnecessary comparisons.

Example 1 – Remove Duplicates from Sorted Array

Problem
Given a sorted array, remove duplicates in‑place and return the new length.

Idea
fast scans elements, slow marks the position to write unique values.

Code

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

Example 2 – Check Subsequence

Problem
Check if string s is a subsequence of string t.

Idea
slow tracks position in s, fast scans t.

Code

def is_subsequence(s, t):
    slow = 0
    for fast in range(len(t)):
        if slow < len(s) and s[slow] == t[fast]:
            slow += 1
    return slow == len(s)

Example 3 – Longest Substring Without Repeating Characters

Problem
Find the length of the longest substring without repeating characters.

Idea
Both left and right move forward; a set maintains uniqueness.

Code

def longest_unique_substring(s):
    seen = set()
    left = 0
    max_len = 0

    for right in range(len(s)):
        while s[right] in seen:
            seen.remove(s[left])
            left += 1
        seen.add(s[right])
        max_len = max(max_len, right - left + 1)

    return max_len

Example 4 – Merge Two Sorted Arrays

Problem
Merge two sorted arrays into one sorted array.

Idea
Pointer i for the first array, pointer j for the second array; both move forward.

Code

def merge_sorted(a, b):
    i = j = 0
    result = []

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

    result.extend(a[i:])
    result.extend(b[j:])
    return result

Difference from Sliding Window

FeatureTwo Pointers (Same Direction)Sliding Window
Window Concept❌ Optional✅ Required
ConditionSimple comparisonConstraint‑based
Use CaseFiltering / matchingOptimization
Typical ExampleRemove duplicatesLongest subarray

Common Mistakes

  • Moving both pointers blindly.
  • Forgetting pointer bounds.
  • Using it for unsorted data when order matters.
  • Confusing it with opposite‑direction pointers.

How to Identify This Pattern

Ask yourself:

  • Do I need to scan forward once?
  • Can one pointer track progress while the other scans ahead?
  • Is order important?

If you can replace a nested loop with such a scan, the pattern applies.

Mental Model

Imagine:

  • One pointer walks forward.
  • Another pointer records a reference.
  • Both move forward, never backward.

Summary

AspectTwo Pointers (Same Direction)
PointersBoth move left → right
SpeedDifferent (fast vs. slow)
TimeO(N)
SpaceO(1)
Best ForFiltering, matching, merging

Final Thought

This pattern bridges the gap between brute‑force and optimal solutions.
Once mastered, you’ll instinctively replace nested loops with clean linear scans.

Back to Blog

Related posts

Read more »

Python: Tprof, a Targeting Profiler

Python: introducing tprof, a targeting profiler Profilers measure the performance of a whole program to identify where most of the time is spent. But once you’...