Two pointers and sliding window are two of the most frequently tested techniques in coding interviews. They turn brute force O(n^2) solutions into elegant O(n) solutions. In this article, you will learn converging pointers, same-direction pointers, fast/slow pointers, and both fixed-size and variable-size sliding windows.

We show core examples in Kotlin, Python, and Go.

Two Pointers: Converging (Opposite Direction)

Two pointers start at opposite ends of the array and move toward each other.

Two Sum II (Sorted Array)

Given a sorted array, find two numbers that add up to a target.

Python:

def two_sum_sorted(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        current_sum = nums[left] + nums[right]
        if current_sum == target:
            return [left, right]
        elif current_sum < target:
            left += 1    # Need a bigger sum
        else:
            right -= 1   # Need a smaller sum
    return []

Kotlin:

fun twoSumSorted(nums: IntArray, target: Int): IntArray {
    var left = 0
    var right = nums.size - 1
    while (left < right) {
        val sum = nums[left] + nums[right]
        when {
            sum == target -> return intArrayOf(left, right)
            sum < target -> left++
            else -> right--
        }
    }
    return intArrayOf()
}

Go:

func twoSumSorted(nums []int, target int) []int {
    left, right := 0, len(nums)-1
    for left < right {
        sum := nums[left] + nums[right]
        if sum == target {
            return []int{left, right}
        } else if sum < target {
            left++
        } else {
            right--
        }
    }
    return nil
}

Time: O(n). Space: O(1).

Note: LeetCode #167 (Two Sum II) expects 1-based indices in its return value. If you are solving that specific problem, return [left + 1, right + 1] instead. The code above returns 0-based indices, which is the general convention for all other problems.

Container With Most Water

Given heights of lines, find the two lines that hold the most water.

Python:

def max_area(heights):
    left, right = 0, len(heights) - 1
    result = 0
    while left < right:
        width = right - left
        height = min(heights[left], heights[right])
        result = max(result, width * height)
        if heights[left] < heights[right]:
            left += 1
        else:
            right -= 1
    return result

Time: O(n). Space: O(1).

3Sum

Find all unique triplets that sum to zero. Sort first, then use two pointers for the inner loop.

Python:

def three_sum(nums):
    nums.sort()
    result = []
    for i in range(len(nums) - 2):
        if i > 0 and nums[i] == nums[i - 1]:
            continue  # Skip duplicates

        left, right = i + 1, len(nums) - 1
        while left < right:
            total = nums[i] + nums[left] + nums[right]
            if total == 0:
                result.append([nums[i], nums[left], nums[right]])
                while left < right and nums[left] == nums[left + 1]:
                    left += 1  # Skip duplicates
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1  # Skip duplicates
                left += 1
                right -= 1
            elif total < 0:
                left += 1
            else:
                right -= 1
    return result

Time: O(n^2). Space: O(1) excluding output. Sorting is O(n log n), but the nested loop dominates.

Two Pointers: Same Direction

Both pointers move in the same direction, usually from left to right.

Remove Duplicates from Sorted Array

Remove duplicates in-place and return the new length.

Python:

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

Kotlin:

fun removeDuplicates(nums: IntArray): Int {
    if (nums.isEmpty()) return 0
    var slow = 0
    for (fast in 1 until nums.size) {
        if (nums[fast] != nums[slow]) {
            slow++
            nums[slow] = nums[fast]
        }
    }
    return slow + 1
}

Go:

func removeDuplicates(nums []int) int {
    if len(nums) == 0 {
        return 0
    }
    slow := 0
    for fast := 1; fast < len(nums); fast++ {
        if nums[fast] != nums[slow] {
            slow++
            nums[slow] = nums[fast]
        }
    }
    return slow + 1
}

Time: O(n). Space: O(1).

Fast and Slow Pointers

One pointer moves one step at a time, the other moves two steps. This technique detects cycles and finds middle elements.

Linked List Cycle Detection (Floyd’s Algorithm)

Python:

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True
    return False

Find Middle of Linked List

Python:

def find_middle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow  # slow is at the middle

Happy Number

A number is happy if repeatedly replacing it with the sum of squares of its digits eventually reaches 1.

Python:

def is_happy(n):
    def get_next(num):
        total = 0
        while num > 0:
            digit = num % 10
            total += digit * digit
            num //= 10
        return total

    slow = n
    fast = get_next(n)
    while fast != 1 and slow != fast:
        slow = get_next(slow)
        fast = get_next(get_next(fast))
    return fast == 1

Time: O(d²) where d is the number of digits. The cycle detection (Floyd’s algorithm) guarantees O(1) space.

Sliding Window: Fixed Size

A window of fixed size k slides across the array. Maintain a running calculation as the window moves.

Maximum Sum Subarray of Size K

Python:

def max_sum_subarray(nums, k):
    window_sum = sum(nums[:k])
    result = window_sum
    for i in range(k, len(nums)):
        window_sum += nums[i] - nums[i - k]  # Add right, remove left
        result = max(result, window_sum)
    return result

Kotlin:

fun maxSumSubarray(nums: IntArray, k: Int): Int {
    var windowSum = nums.take(k).sum()
    var result = windowSum
    for (i in k until nums.size) {
        windowSum += nums[i] - nums[i - k]
        result = maxOf(result, windowSum)
    }
    return result
}

Go:

func maxSumSubarray(nums []int, k int) int {
    windowSum := 0
    for i := 0; i < k; i++ {
        windowSum += nums[i]
    }
    result := windowSum
    for i := k; i < len(nums); i++ {
        windowSum += nums[i] - nums[i-k]
        if windowSum > result {
            result = windowSum
        }
    }
    return result
}

Time: O(n). Space: O(1).

Sliding Window: Variable Size

The window expands and shrinks to find the optimal substring or subarray.

Template for Variable-Size Sliding Window

Python:

def sliding_window_template(s):
    window = {}  # or counter
    left = 0
    result = 0

    for right in range(len(s)):
        # Expand: add s[right] to window
        window[s[right]] = window.get(s[right], 0) + 1

        # Shrink: while window violates your constraint (implement based on problem)
        # Example: while len(window) > k — replace with your actual condition
        while window_is_invalid():  # replace window_is_invalid() with your condition
            # Remove s[left] from window
            window[s[left]] -= 1
            if window[s[left]] == 0:
                del window[s[left]]
            left += 1

        # Update result
        result = max(result, right - left + 1)

    return result

Longest Substring Without Repeating Characters

Python:

def length_of_longest_substring(s):
    char_set = set()
    left = 0
    result = 0
    for right in range(len(s)):
        while s[right] in char_set:
            char_set.remove(s[left])
            left += 1
        char_set.add(s[right])
        result = max(result, right - left + 1)
    return result

Kotlin:

fun lengthOfLongestSubstring(s: String): Int {
    val charSet = mutableSetOf<Char>()
    var left = 0
    var result = 0
    for (right in s.indices) {
        while (s[right] in charSet) {
            charSet.remove(s[left])
            left++
        }
        charSet.add(s[right])
        result = maxOf(result, right - left + 1)
    }
    return result
}

Go:

func lengthOfLongestSubstring(s string) int {
    charSet := make(map[byte]bool)
    left, result := 0, 0
    for right := 0; right < len(s); right++ {
        for charSet[s[right]] {
            delete(charSet, s[left])
            left++
        }
        charSet[s[right]] = true
        if right-left+1 > result {
            result = right - left + 1
        }
    }
    return result
}

Time: O(n). Space: O(min(n, alphabet size)).

Minimum Window Substring

Find the smallest window in s that contains all characters of t.

Python:

from collections import Counter

def min_window(s, t):
    if not t or not s:
        return ""

    need = Counter(t)
    have = {}
    have_count, need_count = 0, len(need)
    result = ""
    result_len = float("inf")
    left = 0

    for right in range(len(s)):
        char = s[right]
        have[char] = have.get(char, 0) + 1

        if char in need and have[char] == need[char]:
            have_count += 1

        while have_count == need_count:
            window_len = right - left + 1
            if window_len < result_len:
                result_len = window_len
                result = s[left:right + 1]

            have[s[left]] -= 1
            if s[left] in need and have[s[left]] < need[s[left]]:
                have_count -= 1
            left += 1

    return result

Time: O(n + m) where n = len(s), m = len(t). Space: O(m).

How to Recognize These Patterns

If the problem says…Try…
“Sorted array” + “find pair”Two pointers (converging)
“Remove in-place”Two pointers (same direction)
“Cycle detection”Fast and slow pointers
“Subarray of size k”Fixed sliding window
“Longest/shortest substring with condition”Variable sliding window
“Contiguous subarray”Sliding window
“Palindrome”Two pointers from both ends

Common Mistakes

  1. Using two pointers on unsorted data when the algorithm requires sorted input. Converging two pointers for Two Sum only works on sorted arrays.

  2. Forgetting to handle duplicates in 3Sum. Skip duplicate values for all three pointers to avoid duplicate triplets.

  3. Not shrinking the sliding window correctly. Make sure you remove the left element before moving the left pointer.

  4. Using a fixed-size window when the problem needs a variable-size window. “Longest substring” needs variable size. “Sum of k elements” needs fixed size.

Practice Problems

ProblemTechniqueLeetCode #
Two Sum IIConverging pointers#167
3SumSort + converging#15
Container With Most WaterConverging pointers#11
Remove DuplicatesSame direction#26
Linked List CycleFast/slow pointers#141
Maximum Average SubarrayFixed window#643
Longest Substring Without RepeatingVariable window#3
Minimum Window SubstringVariable window#76

What’s Next?

Two pointers handle many array and string problems, but some problems require exploring all possibilities. The next article covers Recursion and Backtracking — how to systematically try all options and build solutions step by step.

Next: DSA Tutorial #15: Recursion and Backtracking

Full series: DSA from Zero to Interview Ready