Skip to content

Two Pointer Technique

O(n) array traversal using two coordinated pointers


Overview

The Two Pointer technique uses two pointers to iterate through a data structure, typically an array or linked list. By coordinating the movement of these pointers, we can solve problems that would otherwise require nested loops in linear time.

Visual Representation

Two Pointer Technique Visualization

When to Use Two Pointers

ScenarioPointer MovementExample Problems
Sorted arrayOpposite ends, move toward centerTwo Sum II, Container with Most Water
Fast/slowSame direction, different speedsCycle detection, Middle of linked list
PartitionOne scans, one tracks boundaryRemove duplicates, Move zeros
MergeBoth move forward through separate arraysMerge sorted arrays

Core Patterns

Pattern 1: Opposite Direction (Converging Pointers)

Used when working with sorted arrays to find pairs that satisfy a condition.

python
def two_sum_sorted(nums: list[int], target: int) -> list[int]:
    """
    Find two numbers in a sorted array that sum to target.

    Args:
        nums: Sorted array of integers
        target: Target sum

    Returns:
        [index1, index2] (1-indexed) or [-1, -1] if not found

    Time: O(n), Space: O(1)
    """
    left, right = 0, len(nums) - 1

    while left < right:
        current_sum = nums[left] + nums[right]

        if current_sum == target:
            return [left + 1, right + 1]  # 1-indexed
        elif current_sum < target:
            left += 1   # Need larger sum, move left pointer right
        else:
            right -= 1  # Need smaller sum, move right pointer left

    return [-1, -1]


# Example usage
nums = [2, 7, 11, 15]
print(two_sum_sorted(nums, 9))   # Output: [1, 2]
print(two_sum_sorted(nums, 22))  # Output: [2, 4]
java
public int[] twoSumSorted(int[] nums, int target) {
    int left = 0, right = nums.length - 1;

    while (left < right) {
        int sum = nums[left] + nums[right];
        if      (sum == target) return new int[]{left + 1, right + 1};
        else if (sum < target)  left++;
        else                    right--;
    }
    return new int[]{-1, -1};
}

Complexity: Time O(n) · Space O(1)

  • Time: Each iteration moves at least one pointer, and pointers only move inward, guaranteeing at most n iterations total
  • Space: Only uses constant variables for two pointers and sum calculation

Pattern 2: Same Direction (Fast/Slow Pointers)

Used for cycle detection, finding middle element, or partitioning.

python
def find_middle(head):
    """
    Find the middle node of a linked list.

    Uses slow/fast pointer technique:
    - Slow moves 1 step at a time
    - Fast moves 2 steps at a time
    - When fast reaches end, slow is at middle

    Time: O(n), Space: O(1)
    """
    if not head:
        return None

    slow = fast = head

    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next

    return slow


def has_cycle(head) -> bool:
    """
    Detect if a linked list has a cycle (Floyd's Algorithm).

    If there's a cycle, fast pointer will eventually meet slow pointer.

    Time: O(n), Space: O(1)
    """
    if not head:
        return False

    slow = fast = head

    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True

    return False


def find_cycle_start(head):
    """
    Find the node where the cycle begins.

    After detection:
    1. Reset one pointer to head
    2. Move both at same speed
    3. They meet at cycle start

    Time: O(n), Space: O(1)
    """
    if not head:
        return None

    slow = fast = head

    # Detect cycle
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            break
    else:
        return None  # No cycle

    # Find cycle start
    slow = head
    while slow != fast:
        slow = slow.next
        fast = fast.next

    return slow

Complexity: Time O(n) · Space O(1)

  • Time: find_middle and has_cycle traverse the list once; find_cycle_start adds at most another O(n) pass to find the entrance
  • Space: All three algorithms use only two pointer variables regardless of list size

Pattern 3: Partition (Read/Write Pointers)

Used to modify arrays in-place by separating elements.

python
def remove_duplicates(nums: list[int]) -> int:
    """
    Remove duplicates from sorted array in-place.

    Uses write pointer to track position for next unique element.

    Args:
        nums: Sorted array with duplicates

    Returns:
        Length of array with duplicates removed

    Time: O(n), Space: O(1)
    """
    if not nums:
        return 0

    write = 1  # Position to write next unique element

    for read in range(1, len(nums)):
        if nums[read] != nums[read - 1]:
            nums[write] = nums[read]
            write += 1

    return write


def move_zeros(nums: list[int]) -> None:
    """
    Move all zeros to the end while maintaining relative order.

    Uses write pointer to track position for next non-zero element.

    Time: O(n), Space: O(1)
    """
    write = 0  # Position to write next non-zero

    # Move all non-zeros to the front
    for read in range(len(nums)):
        if nums[read] != 0:
            nums[write] = nums[read]
            write += 1

    # Fill remaining positions with zeros
    while write < len(nums):
        nums[write] = 0
        write += 1


def move_zeros_swap(nums: list[int]) -> None:
    """
    Alternative: Swap approach (maintains relative order).

    Time: O(n), Space: O(1)
    """
    write = 0

    for read in range(len(nums)):
        if nums[read] != 0:
            nums[write], nums[read] = nums[read], nums[write]
            write += 1


# Example usage
arr = [0, 1, 0, 3, 12]
move_zeros(arr)
print(arr)  # Output: [1, 3, 12, 0, 0]

Complexity: Time O(n) · Space O(1)

  • Time: remove_duplicates and move_zeros both make a single pass through the array with read pointer
  • Space: In-place modifications using only write and read pointer variables

Classic Problems

Three Sum

python
def three_sum(nums: list[int]) -> list[list[int]]:
    """
    Find all unique triplets that sum to zero.

    Approach:
    1. Sort the array
    2. Fix one element, use two pointers for remaining two
    3. Skip duplicates to avoid duplicate triplets

    Time: O(n^2), Space: O(1) excluding output
    """
    nums.sort()
    result = []
    n = len(nums)

    for i in range(n - 2):
        # Skip duplicates for first element
        if i > 0 and nums[i] == nums[i - 1]:
            continue

        # Early termination: if smallest is positive, no solution possible
        if nums[i] > 0:
            break

        left, right = i + 1, n - 1
        target = -nums[i]

        while left < right:
            current_sum = nums[left] + nums[right]

            if current_sum == target:
                result.append([nums[i], nums[left], nums[right]])

                # Skip duplicates
                while left < right and nums[left] == nums[left + 1]:
                    left += 1
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1

                left += 1
                right -= 1
            elif current_sum < target:
                left += 1
            else:
                right -= 1

    return result


# Example usage
print(three_sum([-1, 0, 1, 2, -1, -4]))
# Output: [[-1, -1, 2], [-1, 0, 1]]
java
public List<List<Integer>> threeSum(int[] nums) {
    Arrays.sort(nums);
    List<List<Integer>> result = new ArrayList<>();
    int n = nums.length;

    for (int i = 0; i < n - 2; i++) {
        if (i > 0 && nums[i] == nums[i - 1]) continue;
        if (nums[i] > 0) break;

        int left = i + 1, right = n - 1;
        while (left < right) {
            int sum = nums[i] + nums[left] + nums[right];
            if (sum == 0) {
                result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                while (left < right && nums[left]  == nums[left  + 1]) left++;
                while (left < right && nums[right] == nums[right - 1]) right--;
                left++;
                right--;
            } else if (sum < 0) {
                left++;
            } else {
                right--;
            }
        }
    }
    return result;
}

Complexity: Time O(n^2) · Space O(1) excluding output

  • Time: Outer loop iterates n times; inner two-pointer search takes O(n) per iteration, giving O(n^2) total
  • Space: Only uses constant space for pointers and indices; output list not counted

Container With Most Water

python
def max_area(height: list[int]) -> int:
    """
    Find the container that holds most water.

    Two pointers start at ends. Move the shorter line inward
    because moving the taller line cannot increase area.

    Time: O(n), Space: O(1)
    """
    left, right = 0, len(height) - 1
    max_water = 0

    while left < right:
        # Calculate area
        width = right - left
        h = min(height[left], height[right])
        area = width * h
        max_water = max(max_water, area)

        # Move the shorter line
        if height[left] < height[right]:
            left += 1
        else:
            right -= 1

    return max_water


# Example usage
heights = [1, 8, 6, 2, 5, 4, 8, 3, 7]
print(max_area(heights))  # Output: 49
java
public int maxArea(int[] height) {
    int left = 0, right = height.length - 1, maxWater = 0;

    while (left < right) {
        int h = Math.min(height[left], height[right]);
        maxWater = Math.max(maxWater, h * (right - left));

        if (height[left] < height[right]) left++;
        else                              right--;
    }
    return maxWater;
}

Complexity: Time O(n) · Space O(1)

  • Time: Single pass with two pointers converging from both ends; each iteration moves one pointer inward
  • Space: Only constant space for pointers and max area tracking

Trapping Rain Water

python
def trap_rain_water(height: list[int]) -> int:
    """
    Calculate water trapped between bars.

    Two pointer approach:
    - Track left_max and right_max
    - Water at position = min(left_max, right_max) - height[i]
    - Process the side with smaller max first

    Time: O(n), Space: O(1)
    """
    if not height:
        return 0

    left, right = 0, len(height) - 1
    left_max = right_max = 0
    water = 0

    while left < right:
        if height[left] < height[right]:
            if height[left] >= left_max:
                left_max = height[left]
            else:
                water += left_max - height[left]
            left += 1
        else:
            if height[right] >= right_max:
                right_max = height[right]
            else:
                water += right_max - height[right]
            right -= 1

    return water


# Example usage
heights = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
print(trap_rain_water(heights))  # Output: 6
java
public int trap(int[] height) {
    if (height == null || height.length == 0) return 0;

    int left = 0, right = height.length - 1;
    int leftMax = 0, rightMax = 0, water = 0;

    while (left < right) {
        if (height[left] < height[right]) {
            if (height[left] >= leftMax) leftMax = height[left];
            else                         water  += leftMax - height[left];
            left++;
        } else {
            if (height[right] >= rightMax) rightMax = height[right];
            else                           water    += rightMax - height[right];
            right--;
        }
    }
    return water;
}

Complexity: Time O(n) · Space O(1)

  • Time: Two pointers converge in single pass; water at each position computed in O(1) using tracked max heights
  • Space: Only constant space for left/right pointers and left_max/right_max variables

Valid Palindrome

python
def is_palindrome(s: str) -> bool:
    """
    Check if string is palindrome (considering only alphanumeric).

    Two pointers from both ends, skip non-alphanumeric characters.

    Time: O(n), Space: O(1)
    """
    left, right = 0, len(s) - 1

    while left < right:
        # Skip non-alphanumeric from left
        while left < right and not s[left].isalnum():
            left += 1
        # Skip non-alphanumeric from right
        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


# Example usage
print(is_palindrome("A man, a plan, a canal: Panama"))  # True
print(is_palindrome("race a car"))  # False

Complexity: Time O(n) · Space O(1)

  • Time: Two pointers traverse string once, skipping non-alphanumeric characters; each character compared at most once
  • Space: Only constant space for two pointer indices

Sort Colors (Dutch National Flag)

python
def sort_colors(nums: list[int]) -> None:
    """
    Sort array containing only 0, 1, 2 in-place.

    Three-way partitioning using three pointers:
    - left: boundary for 0s (next position for 0)
    - right: boundary for 2s (next position for 2)
    - current: scanning pointer

    Time: O(n), Space: O(1)
    """
    left, current, right = 0, 0, len(nums) - 1

    while current <= right:
        if nums[current] == 0:
            nums[left], nums[current] = nums[current], nums[left]
            left += 1
            current += 1
        elif nums[current] == 2:
            nums[current], nums[right] = nums[right], nums[current]
            right -= 1
            # Don't increment current, need to check swapped element
        else:  # nums[current] == 1
            current += 1


# Example usage
colors = [2, 0, 2, 1, 1, 0]
sort_colors(colors)
print(colors)  # Output: [0, 0, 1, 1, 2, 2]
java
public void sortColors(int[] nums) {
    int left = 0, current = 0, right = nums.length - 1;

    while (current <= right) {
        if (nums[current] == 0) {
            int tmp = nums[left]; nums[left] = nums[current]; nums[current] = tmp;
            left++;
            current++;
        } else if (nums[current] == 2) {
            int tmp = nums[current]; nums[current] = nums[right]; nums[right] = tmp;
            right--;
            // Do NOT increment current
        } else {
            current++;
        }
    }
}

Complexity: Time O(n) · Space O(1)

  • Time: Single pass through array; each element examined at most twice (once by current, once after swap)
  • Space: Only three pointer variables (left, current, right) used for in-place partitioning

Algorithm Selection Guide


Common Mistakes

1. Not Handling Duplicates

python
# WRONG: Will return duplicate triplets
for i in range(n - 2):
    left, right = i + 1, n - 1
    # ... missing duplicate skip

# CORRECT: Skip duplicates
for i in range(n - 2):
    if i > 0 and nums[i] == nums[i - 1]:
        continue
    # Also skip duplicates for left/right pointers

2. Wrong Loop Termination

python
# WRONG: Should be < not <=
while left <= right:  # Pointers should not cross
    if left == right:  # Same element, not a pair!
        # ...

# CORRECT: Stop when pointers meet
while left < right:
    # ...

3. Forgetting to Increment/Decrement After Found

python
# WRONG: Infinite loop when match found
if nums[left] + nums[right] == target:
    result.append([nums[left], nums[right]])
    # Missing: left += 1 and right -= 1

# CORRECT: Move both pointers after finding match
if nums[left] + nums[right] == target:
    result.append([nums[left], nums[right]])
    left += 1
    right -= 1

Complexity Summary

PatternTimeSpaceKey Use Cases
Opposite DirectionO(n)O(1)Sorted arrays, pair finding
Fast/SlowO(n)O(1)Cycle detection, find middle
Read/WriteO(n)O(1)In-place modification
Three PointersO(n)O(1)Three-way partition

ProblemDifficultyPattern
Two Sum IIMediumOpposite Direction
3SumMediumOpposite Direction
Container With Most WaterMediumOpposite Direction
Trapping Rain WaterHardOpposite Direction
Remove Duplicates from Sorted ArrayEasyRead/Write
Move ZeroesEasyRead/Write
Sort ColorsMediumThree Pointers
Linked List CycleEasyFast/Slow
Valid PalindromeEasyOpposite Direction

Interview Tips

  1. Identify the pattern: Sorted array? Linked list cycle? In-place modification?
  2. Consider sorting: If unsorted but can be sorted, two pointers might apply
  3. Handle edge cases: Empty array, single element, all same elements
  4. Think about duplicates: Do you need to skip them?
  5. Explain pointer movement: Why move left vs right? Why fast vs slow?

References