What is Two Pointer Approach?
The Two Pointer Approach is a simple yet powerful technique in algorithms where two indices traverse data structures (like arrays or strings) in different ways to solve problems efficiently in linear (or near-linear) time with constant extra space.
In this article, we deep-dive explains what Two Pointer is, why Two Pointer works, when to use Two Pointer, step-by-step patterns, robust code examples, real-world use cases, pitfalls and advanced extensions so beginners and experienced engineers both get practical value.
Why Use Two Pointers?
- Efficiency: Reduces time complexity in problems involving searching, sorting or pair checking. Often reduces from O(n²) to O(n) or O(n log n).
- Simplicity: Makes the logic cleaner and easier to follow once you understand the technique.
- Memory Friendly: Doesn’t usually need extra space (often works in-place).
- Versatility: Works in arrays, linked lists and strings.
How It Works (Basic Idea)
Imagine two fingers (pointers) placed at different positions in a sequence (start, end or both moving towards each other).
- Move one pointer based on a condition.
- Move the other pointer based on another condition.
- Stop when they meet or when a condition is satisfied.
Two Pointer Core patterns
1. Left–Right Pointers
In this pattern, two pointers start at opposite ends of the array (one at the beginning, one at the end) and move towards each other.
When to Use : Use when array is sorted (or you sort it first). Typical problem: find two numbers that add to target.
- Two Sum in a Sorted Array
- Container With Most Water → Maximize area between two lines.
- Trapping Rain Water (optimized) → Using left & right boundary logic.
In this pattern, two pointers traverse the structure at different speeds , typically one moves one step at a time (slow), while the other moves two steps at a time (fast).
2. Fast–Slow Pointers
One moves faster than the other to detect cycles or find midpoints. Use two pointers at different speeds. Detect cycles or find middle.
- Both pointers start at the same position.
- Advance the fast pointer at 2x the speed of the slow pointer.
- If a cycle exists, they will eventually meet inside the loop.
- If no cycle exists, the fast pointer will reach the end.
- For midpoint detection, when the fast pointer reaches the end, the slow pointer will be at the middle.
- Detecting cycles in linked lists or arrays.
- Finding the middle element of a linked list.
- Identifying meeting points in iterative processes.
- Floyd’s Cycle Detection (Tortoise and Hare Algorithm)
- Finding the Middle of a Linked List
- Happy Number Problem (detecting cycles in number transformations)
This pattern is extremely time-efficient (O(n)) and uses constant space (O(1)), making it a favorite for linked list and cycle detection interview problems.
3. Sliding Window Pointers
In this pattern, two pointers move in the same direction to maintain a dynamic “window” over a subarray or substring. One pointer represents the start of the window and the other represents the end.
- Start with both pointers at the beginning.
- Expand the end pointer to grow the window and include new elements.
- If the window becomes invalid (violates a condition), contract the start pointer until it becomes valid again.
- Track the best answer (length, sum, count, etc.) during the process.
- You need to examine contiguous subarrays or substrings.
- The problem involves constraints like sum, distinct characters or maximum/minimum window size.
- Typical in problems asking for the longest, shortest or count of subarrays that satisfy a condition.
- Longest Substring Without Repeating Characters
- Minimum Window Substring
- Maximum Sum Subarray of Size K
- Subarray Product Less Than K
This is one of the most versatile interview patterns, as it transforms many brute-force O(n²) solutions into efficient O(n) ones.
If you’d like a complete breakdown of the Sliding Window technique. check out our in-depth guide here: Read the full Sliding Window article
4. Partitioning (Dutch National Flag)
This pattern uses multiple pointers to partition an array into distinct regions in a single pass. It’s most famously applied to the Dutch National Flag problem: sorting an array of 0s, 1s, and 2s without extra space.
- Maintain three regions
- Left region for smaller values (e.g., 0s).
- Middle region for current elements under consideration.
- Right region for larger values (e.g., 2s).
- Use two or three pointers (low, mid, high) to expand/contract regions as you scan the array once.
- You need to group elements into categories (e.g., colors, ranges, 0/1/2 values).
- You want an O(n) time, O(1) space solution.
- Dutch National Flag Problem (sort 0s, 1s, 2s).
- Partitioning Around a Pivot (used in QuickSort).
- Move Negatives to One Side or Segregate Even/Odd Numbers.
This pattern is very interview-friendly, since it demonstrates mastery of in-place array manipulation and pointer-based thinking.
Example 1 — Two-Sum for a sorted array (left–right pointers)
Given sorted arr[], find indices of two elements whose sum equals target. If multiple answers, return the pair with smallest indices. Return (-1, -1) if none.
Intuition: Start L=0, R=n-1. If arr[L] + arr[R] is too small, move L right to increase sum; if too big, move R left to decrease sum.
import java.util.*; class TwoSumSorted { static int[] twoSumSorted(long[] arr, long target) { int L = 0, R = arr.length - 1; while (L < R) { long s = arr[L] + arr[R]; if (s == target) return new int[]{L, R}; if (s < target) L++; else R--; } return new int[]{-1, -1}; } }
If array not sorted and you must preserve original indices, either
- Use a hash map in O(n) time and O(n) space (see below), or
- Sort pairs (value, original_index) and then apply two-pointer (O(n log n) + O(n) space/time).
Example 2 — Two-Sum for an unsorted array (hash map)
Given an unsorted array nums and a target sum, return the indices of two numbers that add up to the target.
import java.util.HashMap; public class TwoSum { public static int[] twoSumUnsorted(int[] nums, int target) { // HashMap to store value -> index HashMap<Integer, Integer> seen = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int need = target - nums[i]; if (seen.containsKey(need)) { // Found the pair return new int[]{seen.get(need), i}; } seen.put(nums[i], i); } // If no pair found return new int[]{-1, -1}; } public static void main(String[] args) { int[] nums = {3, 5, 2, 8, 11}; int target = 10; int[] result = twoSumUnsorted(nums, target); System.out.println("Indices: [" + result[0] + ", " + result[1] + "]"); } }
Example 3 — Middle of singly linked list (slow/fast)
Given a Linked List , Your task is to return the middle node (if even length, choose the second middle).
Idea: Move slow by 1 and fast by 2. When fast hits the end, slow is at middle.
class ListNode { int val; ListNode next; ListNode(int val) { this.val = val; } } public class MiddleOfLinkedList { public static ListNode findMiddle(ListNode head) { if (head == null) return null; ListNode slow = head; ListNode fast = head; // Move fast by 2 and slow by 1 while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; // slow is at the middle } public static void main(String[] args) { // Example list: 1->2->3->4->5 ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3); head.next.next.next = new ListNode(4); head.next.next.next.next = new ListNode(5); ListNode middle = findMiddle(head); System.out.println("Middle node value: " + middle.val); } }
Example 4 — Reverse a singly linked list (prev/current)
Given the head of a singly linked list, reverse it so that the last node becomes the head.
To reverse a linked list, we use two pointers: prev and curr. At the start, prev is null and curr is the head. In each step, we keep track of the next node, then flip the current node’s pointer to point back to prev. After that, we move both pointers forward. We repeat this until we reach the end of the list. Finally, prev becomes the new head. This way, we reverse the entire list in one pass using constant extra space.
class ListNode { int val; ListNode next; ListNode(int val) { this.val = val; } } public class ReverseLinkedList { public static ListNode reverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode next = curr.next; // Save next curr.next = prev; // Reverse link prev = curr; // Move prev curr = next; // Move curr } return prev; // New head } public static void main(String[] args) { // Create list: 1->2->3->4->5 ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3); head.next.next.next = new ListNode(4); head.next.next.next.next = new ListNode(5); ListNode newHead = reverseList(head); // Print reversed list ListNode temp = newHead; while (temp != null) { System.out.print(temp.val + " "); temp = temp.next; } } }
Example 5 — Longest subarray with sum ≤ S (Sliding Window)
Given an array of integers and a number S, find the length of the longest contiguous subarray whose sum is less than or equal to S.
To solve this, we use the sliding window pattern with two pointers: left and right. We start with both pointers at the beginning of the array and a variable sum = 0 to track the current window sum. We expand the window by moving right forward and adding nums[right] to sum. If the sum exceeds S, we shrink the window from the left by moving left forward and subtracting nums[left] from sum until the sum becomes ≤ S again. At each step, we update the maximum length of a valid window. This approach ensures we traverse the array only once and use constant extra space, achieving an optimal O(n) solution.
public class LongestSubarraySum { public static int longestSubarrayWithSumAtMostS(int[] nums, int S) { int left = 0, sum = 0, maxLen = 0; for (int right = 0; right < nums.length; right++) { sum += nums[right]; // Expand window // Shrink window if sum exceeds S while (sum > S) { sum -= nums[left]; left++; } // Update maximum length maxLen = Math.max(maxLen, right - left + 1); } return maxLen; } public static void main(String[] args) { int[] nums = {1, 2, 1, 0, 1, 1, 0}; int S = 4; System.out.println(longestSubarrayWithSumAtMostS(nums, S)); // Output: 5 } }
Example 6 — Dutch National Flag (three pointers)
Given an array containing only the values {0, 1, 2}, rearrange it in-place so that all 0s come first, followed by all 1s, and then all 2s.
To solve this efficiently, we use three pointers: low, mid and high.
- low marks the boundary of 0s, mid is the current element under consideration, and high marks the boundary of 2s.
- We start with low = 0, mid = 0, and high = n - 1. Then we process elements at mid
- If nums[mid] == 0, swap it with nums[low], then move both low and mid forward.
- If nums[mid] == 1, it is already in the correct region, so just move mid forward.
- If nums[mid] == 2, swap it with nums[high] and move high backward, without moving mid, because the new element at mid needs to be checked.
- We repeat this process until mid crosses high. This ensures that all 0s, 1s and 2s are grouped correctly in a single pass using constant space.
public class DutchNationalFlag { public static void sortNumbers(int[] nums) { int low = 0, mid = 0, high = nums.length - 1; while (mid <= high) { if (nums[mid] == 0) { // Swap mid and low int temp = nums[low]; nums[low] = nums[mid]; nums[mid] = temp; low++; mid++; } else if (nums[mid] == 1) { mid++; } else { // nums[mid] == 2 // Swap mid and high int temp = nums[mid]; nums[mid] = nums[high]; nums[high] = temp; high--; } } } public static void main(String[] args) { int[] nums = {2, 0, 2, 1, 1, 0}; sortNumbers(nums); for (int num : nums) { System.out.print(num + " "); } // Output: 0 0 1 1 2 2 } }
Short recap (cheat sheet)
- If array is sorted → try left–right pointers for O(n).
- If array unsorted and you need indices → hash map for O(n) space/time.
- If linked list → use slow/fast for middle & cycle detection.
- Window problems → sliding window pattern (expand/contract).
- Partition problems → multi-pointer in one pass (Dutch flag).
- Watch for overflow, off-by-one and duplicates.
Two-Pointer Approach FAQs
Q: What is the Two-Pointer Approach?
It’s a technique where you use two indices (or pointers) to traverse a data structure (like an array, string, or linked list) in a controlled way. This allows many problems to be solved in O(n) or better instead of O(n²).
Q: Does it always beat hashing?
Not always, Two-pointers often give O(1) space solutions. Hashing may give faster lookups but requires extra memory.
Use two-pointers when memory is tight or when sorted order helps. Use hashing when you must preserve original indices in unsorted data.
Q: Can it handle negative numbers?
Yes. As long as your logic (increasing/decreasing pointer movements) correctly accounts for negative values, it works fine.
Q: Is it limited to arrays?
No. It works equally well for, Strings (palindrome check, substring problems), Linked lists (middle element, cycle detection, reversal), Intervals & ranges in scheduling and greedy algorithms
Q: Can it detect cycles in linked lists?
Yes. Using the fast–slow pointer (tortoise and hare) method, you can detect cycles and even find the starting node of the cycle.
Q: Is recursion better than two pointers?
Usually no. Recursion adds stack overhead and may risk stack overflow for very large inputs. Two pointers keep memory usage at O(1).
Q: What about parallelization?
The pointer movement itself is sequential. But two-pointer logic can be embedded in parallelized pipelines (e.g., merging sorted chunks in parallel).
Q: What’s a sliding window?
A variant of two-pointers where both pointers move in the same direction to maintain a dynamic "window" (subarray/subsequence) that satisfies some condition (e.g., longest substring without repeating characters).
Q: Can I use it in competitive programming?
Absolutely. It’s a staple technique fast, memory efficient and widely applicable. Many classic problems (two-sum, 3-sum, max subarray, palindrome checks, cycle detection) rely on two-pointers.