DEV Community

Cover image for Master Coding Interviews : Part 2 ( Two Pointers Pattern )
Messaoud Wael
Messaoud Wael

Posted on

Master Coding Interviews : Part 2 ( Two Pointers Pattern )

If you read Part 1 of this series, you already know how a single pattern can transform a slow, brute-force solution into an elegant, optimized one. Today we're going to do the same thing — but with a different tool in our belt: the Two Pointers pattern.

This one is everywhere in coding interviews. Once you recognize it, you'll start seeing it in problems you might have struggled with before.


What is the Two Pointers Pattern?

The idea is simple: instead of using one index to iterate through your data structure, you use two. These two pointers move through the array (or string) according to specific conditions, and together they help you find the answer without needing nested loops.

There are two main variants you'll encounter:

  • Opposite-direction pointers : One pointer starts at the beginning, the other at the end. They move toward each other until they meet.
  • Same-direction pointers (fast & slow) : Both pointers start at the same end, but move at different speeds or under different conditions.

Let's jump straight into a problem to see the first variant in action.


Problem 1 — Two Sum II: Input Array Is Sorted

Two Sum II: Input Array Is Sorted

Given a 1-indexed array of integers numbers that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target number. Return the indices of the two numbers as an integer array [index1, index2] of length 2.

The brute-force approach is fairly intuitive: try every possible pair and check whether it adds up to the target.

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        for i in range(len(numbers)):
            for j in range(i + 1, len(numbers)):
                if numbers[i] + numbers[j] == target:
                    return [i + 1, j + 1]  # 1-indexed result
        return []
Enter fullscreen mode Exit fullscreen mode

This works — but just like in Part 1, it hides a performance problem we can't ignore:

  • Time complexity : O(n²) : For an array of 30,000 elements, this is potentially 900 million comparisons.
  • Time Limit Exceeded : For large inputs on LeetCode, this solution will almost certainly hit the TLE wall.

  • In a real production context, a quadratic loop like this over large datasets is a performance bottleneck waiting to happen.


Optimization through the Two Pointers Pattern

Here's the key insight we're missing in the brute-force: the array is already sorted. That's a hint the problem is giving us for free, and the Two Pointers pattern is exactly how we take advantage of it.

We place one pointer at the very beginning of the array (left) and one at the very end (right). Then we look at their sum:

  • If the sum equals the target → we're done.
  • If the sum is too large → we need a smaller number, so we move right one step to the left.
  • If the sum is too small → we need a larger number, so we move left one step to the right.

Each iteration eliminates a candidate, and since both pointers are converging toward the center, we'll find the answer — or confirm it doesn't exist — in a single pass.

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        left = 0                    # Left pointer starts at the beginning
        right = len(numbers) - 1   # Right pointer starts at the end

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

            if current_sum == target:
                # Found the pair — return 1-indexed positions
                return [left + 1, right + 1]

            elif current_sum > target:
                # Sum is too large: move right pointer inward to get a smaller value
                right -= 1

            else:
                # Sum is too small: move left pointer inward to get a larger value
                left += 1

        return []  # No solution found (problem guarantees one exists, but good practice)
Enter fullscreen mode Exit fullscreen mode

This brings us down to O(n) time complexity and O(1) space complexity — a dramatic improvement over the brute-force approach, and it passes LeetCode with no issues.


Variant 2 — Same-Direction Pointers

The second variant doesn't converge from opposite ends. Instead, both pointers start from the same side and move in the same direction, but at different speeds or under different conditions. This is perfect for problems where you need to restructure an array in-place or detect a specific pattern as you scan forward.

Let's look at a classic example.


Problem 2 — Remove Duplicates from Sorted Array

Remove Duplicates from Sorted Array

Given an integer array nums sorted in non-decreasing order, remove the duplicates in-place such that each unique element appears only once. Return the number of unique elements k.

The naïve instinct here might be to use a set to collect unique values and rebuild the array — but the problem explicitly requires an in-place solution using O(1) extra memory. A set would cost us O(n) space, which defeats the purpose.

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        seen = set()
        result = []

        for num in nums:
            if num not in seen:
                seen.add(num)
                result.append(num)

        # This doesn't satisfy the in-place constraint
        for i in range(len(result)):
            nums[i] = result[i]

        return len(result)
Enter fullscreen mode Exit fullscreen mode

Beyond the space constraint violation, this approach creates auxiliary data structures we don't need. The Two Pointers pattern gives us a cleaner — and compliant — solution.


Optimization: Same-Direction Two Pointers

Here's the idea: we use a slow pointer to track the position where the next unique element should be written, and a fast pointer to scan ahead through the array looking for new unique values. Whenever fast finds a value different from what slow is pointing at, we advance slow and write that new value there.

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        # 'slow' marks the boundary of our unique-elements section
        slow = 0

        # 'fast' scans ahead to discover new unique elements
        for fast in range(1, len(nums)):

            # When fast finds a value different from slow's position,
            # a new unique element has been discovered
            if nums[fast] != nums[slow]:
                slow += 1                  # Expand the unique section
                nums[slow] = nums[fast]    # Write the new unique value in place

        # slow is now the index of the last unique element
        # so the count of unique elements is slow + 1
        return slow + 1
Enter fullscreen mode Exit fullscreen mode

Time complexity : O(n)fast does a single pass through the array.

Space complexity : O(1) — everything is done in-place, no extra data structures.

The elegance here is that the two pointers work as a team: fast does the exploration, slow does the writing. They never interfere with each other because slow can only be at or behind fast.


When to use this pattern

  • When dealing with sorted arrays or strings (a big hint for opposite-direction pointers)
  • When the problem asks you to find a pair (or triplet) of elements that satisfy a condition
  • When you need to restructure or deduplicate an array in-place with O(1) space
  • When the brute-force solution involves nested loops over the same linear data structure — that's almost always a signal that two pointers can collapse it to O(n)

What's next ? Practice

Head over to LeetCode and try the Two Pointers problem list — there are over 200 problems tagged with this pattern. Start with the easy ones to build your instinct, then move into medium difficulty.

A few good ones to try next:

  • Container With Most Water (Medium) — opposite-direction pointers
  • 3Sum (Medium) — two pointers inside an outer loop
  • Linked List Cycle (Easy) — fast & slow pointers on a linked list

PS: As with the Sliding Window pattern, Two Pointers isn't always the only valid approach. It's a powerful tool when certain conditions are met — sorted data, in-place constraints, pair/subset problems — but always think about whether the pattern genuinely fits before reaching for it.

See you in the next article for another coding pattern !

Top comments (0)