DSAJune 8, 20256 min read

Mastering the Two Pointers Pattern in Java

Two pointers is one of the most versatile DSA patterns. Here's every variant — opposite ends, fast-slow, and multi-array — with Java solutions and when to reach for each.

DSAJavaLeetCodeArraysAlgorithms

Two pointers shows up in at least one problem every LeetCode contest. Once you recognize the three variants, you'll solve these in under 10 minutes. Let me break each one down.

Variant 1: Opposite Ends (Two-Sum style)

Start one pointer at index 0 and one at n-1. Move them toward each other based on a condition. Classic use: two-sum on a sorted array, container with most water.

TwoSum.java
java
1// Two Sum II — sorted array, O(n) time O(1) space
2public int[] twoSum(int[] numbers, int target) {
3    int left = 0, right = numbers.length - 1;
4    while (left < right) {
5        int sum = numbers[left] + numbers[right];
6        if (sum == target)  return new int[]{left + 1, right + 1};
7        if (sum < target)   left++;
8        else                right--;
9    }
10    return new int[]{};
11}

Variant 2: Fast and Slow Pointers

One pointer moves 1 step, one moves 2 steps. Used to detect cycles in linked lists and find the middle node. Floyd's algorithm is the canonical example.

LinkedListCycle.java
java
1public boolean hasCycle(ListNode head) {
2    ListNode slow = head, fast = head;
3    while (fast != null && fast.next != null) {
4        slow = slow.next;
5        fast = fast.next.next;
6        if (slow == fast) return true;
7    }
8    return false;
9}
10
11// Find middle of linked list
12public ListNode middleNode(ListNode head) {
13    ListNode slow = head, fast = head;
14    while (fast != null && fast.next != null) {
15        slow = slow.next;
16        fast = fast.next.next;
17    }
18    return slow; // slow is at middle when fast reaches end
19}

Variant 3: Same Direction (Remove Duplicates)

Both pointers start at 0. The slow pointer tracks the position of the next valid element; the fast pointer scans ahead. Used for in-place array modifications.

RemoveDuplicates.java
java
1public int removeDuplicates(int[] nums) {
2    if (nums.length == 0) return 0;
3    int slow = 0;
4    for (int fast = 1; fast < nums.length; fast++) {
5        if (nums[fast] != nums[slow]) {
6            slow++;
7            nums[slow] = nums[fast];
8        }
9    }
10    return slow + 1;
11}

Recognition signal: the problem involves a sorted array, a linked list, or asks for pairs/triplets that satisfy a condition. If brute force is O(n²), two pointers often gives O(n).

3Sum — Combining Sort + Two Pointers

3Sum is a classic extension. Sort the array first, fix one element with a loop, then use two pointers for the remaining pair. Sorting costs O(n log n) but removes the need for a hash set and handles duplicates cleanly.

ThreeSum.java
java
1public List<List<Integer>> threeSum(int[] nums) {
2    Arrays.sort(nums);
3    List<List<Integer>> result = new ArrayList<>();
4
5    for (int i = 0; i < nums.length - 2; i++) {
6        if (i > 0 && nums[i] == nums[i - 1]) continue; // skip duplicates
7
8        int left = i + 1, right = nums.length - 1;
9        while (left < right) {
10            int sum = nums[i] + nums[left] + nums[right];
11            if (sum == 0) {
12                result.add(Arrays.asList(nums[i], nums[left], nums[right]));
13                while (left < right && nums[left]  == nums[left + 1])  left++;
14                while (left < right && nums[right] == nums[right - 1]) right--;
15                left++; right--;
16            } else if (sum < 0) left++;
17            else                right--;
18        }
19    }
20    return result;
21}

Trapping Rain Water

One of the hardest two-pointer problems. Water at position i equals min(maxLeft[i], maxRight[i]) - height[i]. The two-pointer approach avoids precomputing these arrays by tracking running maximums.

TrappingRainWater.java
java
1public int trap(int[] height) {
2    int left = 0, right = height.length - 1;
3    int maxLeft = 0, maxRight = 0, water = 0;
4
5    while (left < right) {
6        if (height[left] < height[right]) {
7            if (height[left] >= maxLeft) maxLeft = height[left];
8            else water += maxLeft - height[left];
9            left++;
10        } else {
11            if (height[right] >= maxRight) maxRight = height[right];
12            else water += maxRight - height[right];
13            right--;
14        }
15    }
16    return water;
17}

When NOT to Use Two Pointers

  • Unsorted arrays where order matters for the condition — sort first or use a hash map instead.
  • When you need all pairs, not just existence — two pointers misses pairs when there are duplicates unless handled carefully.
  • Linked lists where you need O(1) space but must modify nodes — use the slow/fast variant carefully to avoid losing node references.

More in DSA