Two pointers is one of those patterns that quietly shows up in half of the “medium” LeetCode problems and a surprising number of real systems tasks involving streaming, parsing, or array-like data structures. It’s simple to learn, powerful in practice, and often the difference between O(n2)O(n2) brute force and O(n)O(n) production-grade code.
Below is a developer-focused introduction: when to use two pointers, core strategies, and concrete examples in JavaScript and Python you can adapt immediately.
What Is the Two Pointers Technique?
At its core, the two pointers technique means maintaining two indices (or iterators) over a sequence (array, string, linked list) and moving them in a coordinated way to satisfy some condition.
- Time complexity often drops from O(n2)O(n2) (nested loops) to O(n)O(n).
- Space complexity is usually O(1)O(1) extra, aside from outputs.
- Works best on sequential structures and often shines when the data is sorted or can be treated as a stream.
You’ll most commonly see it in:
- Pair problems: “find two numbers that…”
- Interval/window problems: “longest/shortest subarray/substring that…”
- Linked list problems: “find middle node”, “detect cycle”, “reorder list”.
Core Strategies for Two Pointers
Most two-pointer solutions fall into a few patterns that are worth memorizing.
- Opposite ends (inward traversal):Pointers start at the beginning and end of a sequence and move toward each other.Great for pair sums in sorted arrays, palindrome checks, and “squeeze” problems.
- Same direction (sliding / shrinking):Both pointers move left→right, maintaining a window or scanning two sequences.Used for subarray/substring constraints, merging sorted arrays, or matching sequences.
- Fast–slow (different speeds):One pointer moves faster (2x) than the other.Used for cycle detection and finding the middle of a linked list.
Instead of memorizing problems, think of these as templates: “Do I need a pair, a window, or structure-aware traversal?”.
Strategy 1: Opposite Ends on Sorted Arrays
Use case
You have a sorted array and need to find a pair (or reason about pairs) that satisfies a condition, most frequently a target sum.
Example problem
Given a sorted array nums and an integer target, return true if there exist two numbers that add up to target, otherwise false.
Intuition
- Put left at the start, right at the end.
- Compute sum = nums[left] + nums[right].
- If sum is too small, move left right to increase it; if too large, move right left to decrease it.
This leverages the sorted order to prune large parts of the search space.
JavaScript implementation
js// Two-sum in a sorted array using opposite-end pointers
function twoSumSorted(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) {
return true; // Found a pair
} else if (sum < target) {
left++; // Need a bigger sum
} else {
right--; // Need a smaller sum
}
}
return false; // No pair found
}
// Example
console.log(twoSumSorted([-3, -1, 0, 1, 2], -2)); // true
This runs in O(n)O(n) time and O(1)O(1) space, versus O(n2)O(n2) if you tried all pairs with nested loops.
Variation: Trapping or squeezing
The same “squeeze from both ends” pattern is used for problems like:
- Checking if a string is a palindrome (compare s[left] and s[right], then move inward).
- Squaring and sorting an already sorted array of positives and negatives.
Strategy 2: Sliding Window with Two Pointers
Use case
You need a contiguous subarray/substring that satisfies a condition:
- Longest substring without repeating characters.
- Smallest subarray with sum ≥ target.
- Max number of consecutive 1s, etc.
Here, both pointers move in the same direction, defining a window [left, right).
Example problem
Given an array of positive integers nums and an integer target, find the minimal length of a contiguous subarray of which the sum is ≥ target. If there is no such subarray, return 0.
Intuition
- Expand right to grow the window until the sum is ≥ target.
- Once it’s valid, shrink from the left by moving left to try to minimize the length.
- Repeat until right reaches the end.
Python implementation
python# Minimum size subarray sum using sliding window
def min_subarray_len(target, nums):
left = 0
window_sum = 0
min_len = float("inf")
for right in range(len(nums)):
window_sum += nums[right]
# Shrink from the left while window is "good enough"
while window_sum >= target:
min_len = min(min_len, right - left + 1)
window_sum -= nums[left]
left += 1
return 0 if min_len == float("inf") else min_len
# Example
print(min_subarray_len(7, [2,3,1,2,4,3])) # 2 (subarray [4,3])
This is a classic sliding-window two-pointer pattern with O(n)O(n) time and O(1)O(1) extra space.
Strategy 3: Fast–Slow Pointers on Linked Lists
Use case
You’re working with linked lists and need to reason about relative positions without extra memory:
Example problem
Given the head of a singly linked list, determine if it has a cycle.
Intuition
- Maintain two pointers:slow moves one step at a time.fast moves two steps at a time.
- slow moves one step at a time.
- fast moves two steps at a time.
- If there's a cycle, they will eventually meet inside it.
- If fast reaches null, there is no cycle.
Python implementation
pythonclass ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next # 1 step
fast = fast.next.next # 2 steps
if slow == fast:
return True # Met inside a cycle
return False # Reached end, no cycle
This technique runs in linear time and constant space, avoiding extra structures like hash sets.
Mini Case Study: Refactoring Brute Force to Two Pointers
Imagine a coding challenge:
“Given a sorted array of integers and a target, return all unique pairs (a, b) such that a + b = target.”
A naive approach would:
- Loop i from 0..n-1.
- For each i, loop j from i+1..n-1.
- Collect pairs where nums[i] + nums[j] == target.That’s O(n2)O(n2) and redundant, especially on large n.
Refactor with two pointers:
js// Return all unique pairs that sum to target in a sorted array
function twoSumPairs(nums, target) {
const result = [];
let left = 0;
let right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) {
result.push([nums[left], nums[right]]);
// Skip duplicates to keep pairs unique
const leftVal = nums[left];
const rightVal = nums[right];
while (left < right && nums[left] === leftVal) left++;
while (left < right && nums[right] === rightVal) right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
return result;
}
// Example
console.log(twoSumPairs([1,1,2,2,3,4,4,5], 6));
// [ [2,4], [1,5] ] depending on ordering
This version is linear in the size of the array and handles uniqueness by skipping contiguous duplicates—a common pattern in two-pointer solutions on sorted inputs.
Practical Tips and Gotchas
A few patterns and pitfalls that come up repeatedly in interviews and production code.
- Initialize correctly:Opposite ends: left = 0, right = n - 1.Sliding window: left = 0, grow right in a loop.Fast–slow: both at head.
- Opposite ends: left = 0, right = n - 1.
- Sliding window: left = 0, grow right in a loop.
- Fast–slow: both at head.
- Define invariants clearly:For sliding windows, track what your window means (sum ≥ target, all unique chars, etc.).For opposite ends, know which direction increases or decreases your metric.
- For sliding windows, track what your window means (sum ≥ target, all unique chars, etc.).
- For opposite ends, know which direction increases or decreases your metric.
- Handle termination conditions:Opposite ends usually stop at left >= right.Sliding window iterates right once and moves left only forward.Fast–slow stops when pointers meet or fast reaches the end.
- Watch for off-by-one and empty inputs:Always consider [], [1], and “no solution” cases.Decide whether you return indices, values, or a boolean.
- Always consider [], [1], and “no solution” cases.
- Decide whether you return indices, values, or a boolean.
- Prefer two pointers over hash maps when:Input is sorted or cheaply sortable.You want predictable memory usage and cache-friendly, linear scans.
- Input is sorted or cheaply sortable.
- You want predictable memory usage and cache-friendly, linear scans.
Conclusion
Two pointers is one of the highest-leverage patterns you can master for algorithmic interviews and real-world data-processing tasks. It turns many “obvious but slow” nested loops into clean, linear-time solutions by coordinating two indices instead of brute-forcing all pairs.
If you’re practicing, focus on these steps:
- Spot sorted or sequential data and pair/window constraints.
- Decide whether you’re using opposite ends, sliding window, or fast–slow.
- Write down your invariants and termination conditions, then implement carefully.
From there, problems like two-sum variants, palindromes, substring windows, and linked-list cycles all start to look pleasantly similar—which is exactly what you want when the clock is ticking in an interview or performance-critical code review.





