LeetCopilot Logo
LeetCopilot
LeetCode Pattern/Two Pointers/3Sum Duplicate Handling: The Edge Case That Breaks Most Solutions

3Sum Duplicate Handling: The Edge Case That Breaks Most Solutions

LeetCopilot Team
Dec 9, 2025
11 min read
Two Pointers3SumDuplicatesEdge CasesInterview Prep
Your 3Sum solution works on simple test cases but fails when duplicates appear. Learn the exact logic for skipping duplicates in 3Sum, why it's necessary, and how to implement it correctly.

You've solved 3Sum. Your logic is sound: fix one number, use two pointers on the rest. You submit.

Wrong Answer.

The test case: nums = [-1, 0, 1, 2, -1, -4]

Expected: [[-1, -1, 2], [-1, 0, 1]]

Your output: [[-1, -1, 2], [-1, 0, 1], [-1, 0, 1], [-1, -1, 2]]

Duplicates. Your solution returns the same triplet multiple times.

This is the #1 reason 3Sum solutions fail. The problem asks for unique triplets, but duplicate values in the array cause your algorithm to generate duplicate triplets. And simply using a set to deduplicate won't work efficiently.

This guide will teach you the exact logic for skipping duplicates in 3Sum, why it's necessary, and how to implement it correctly.

TL;DR

  • Problem: Duplicate values in the input array cause duplicate triplets in the output
  • Solution: Skip duplicate values when choosing the fixed element and when moving pointers
  • Key insight: After processing a value, skip all subsequent occurrences of that value
  • Implementation: Use while loops to skip duplicates after finding a triplet or moving pointers
  • Common mistake: Skipping duplicates before processing instead of after

Why Duplicates Break 3Sum

The Basic 3Sum Algorithm (Without Duplicate Handling)

python
def threeSum(nums):
    nums.sort()
    result = []
    
    for i in range(len(nums) - 2):
        left = i + 1
        right = len(nums) - 1
        
        while left < right:
            total = nums[i] + nums[left] + nums[right]
            
            if total == 0:
                result.append([nums[i], nums[left], nums[right]])
                left += 1
                right -= 1
            elif total < 0:
                left += 1
            else:
                right -= 1
    
    return result

What Goes Wrong

Input: nums = [-1, 0, 1, 2, -1, -4]

After sorting: [-4, -1, -1, 0, 1, 2]

Execution trace:

code
i=0, nums[i]=-4:
  left=1, right=5: -4 + (-1) + 2 = -3 (too small, left++)
  left=2, right=5: -4 + (-1) + 2 = -3 (too small, left++)
  left=3, right=5: -4 + 0 + 2 = -2 (too small, left++)
  left=4, right=5: -4 + 1 + 2 = -1 (too small, left++)
  left=5, right=5: stop

i=1, nums[i]=-1:
  left=2, right=5: -1 + (-1) + 2 = 0 ✓ Found: [-1, -1, 2]
  left=3, right=4: -1 + 0 + 1 = 0 ✓ Found: [-1, 0, 1]

i=2, nums[i]=-1:  ← DUPLICATE!
  left=3, right=5: -1 + 0 + 2 = 1 (too large, right--)
  left=3, right=4: -1 + 0 + 1 = 0 ✓ Found: [-1, 0, 1] ← DUPLICATE!

i=3, nums[i]=0:
  left=4, right=5: 0 + 1 + 2 = 3 (too large, right--)
  left=4, right=4: stop

Result: [[-1, -1, 2], [-1, 0, 1], [-1, 0, 1]] ← Contains duplicate!

Why? When i=1 and i=2, we're using the same value (-1) as the fixed element, generating the same triplets.

The Solution: Skip Duplicates

We need to skip duplicate values in three places:

  1. When choosing the fixed element (i)
  2. After finding a valid triplet (left pointer)
  3. After finding a valid triplet (right pointer)

The Correct Implementation

python
def threeSum(nums):
    nums.sort()
    result = []
    
    for i in range(len(nums) - 2):
        # Skip duplicate values for i
        if i > 0 and nums[i] == nums[i - 1]:
            continue
        
        left = i + 1
        right = len(nums) - 1
        
        while left < right:
            total = nums[i] + nums[left] + nums[right]
            
            if total == 0:
                result.append([nums[i], nums[left], nums[right]])
                
                # Skip duplicates for left
                while left < right and nums[left] == nums[left + 1]:
                    left += 1
                
                # Skip duplicates for right
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1
                
                # Move both pointers
                left += 1
                right -= 1
                
            elif total < 0:
                left += 1
            else:
                right -= 1
    
    return result

Breaking Down the Duplicate Skipping Logic

1. Skipping Duplicates for the Fixed Element (i)

python
for i in range(len(nums) - 2):
    if i > 0 and nums[i] == nums[i - 1]:
        continue

Why i > 0?

  • We need to compare nums[i] with nums[i-1]
  • When i=0, there's no nums[-1] to compare with (or it's out of our range)
  • So we only skip duplicates starting from i=1

Why compare with nums[i-1]?

  • After sorting, duplicates are adjacent
  • If nums[i] == nums[i-1], we've already processed all triplets starting with this value
  • We can skip this iteration

Example:

code
Array: [-4, -1, -1, 0, 1, 2]
         i=0  i=1  i=2

i=0: nums[0]=-4, process it
i=1: nums[1]=-1, process it
i=2: nums[2]=-1, same as nums[1], SKIP

2. Skipping Duplicates for Left Pointer

python
if total == 0:
    result.append([nums[i], nums[left], nums[right]])
    
    # Skip duplicates for left
    while left < right and nums[left] == nums[left + 1]:
        left += 1
    
    left += 1  # Move to the next different value

Why skip after finding a triplet?

  • We've found a valid triplet with nums[left]
  • If nums[left+1] is the same, it would create a duplicate triplet
  • We skip all duplicates and move to the next different value

Example:

code
Array: [-2, 0, 0, 0, 2]
Fixed i=-2, looking for sum=0

left=1, right=4: -2 + 0 + 2 = 0 ✓ Found: [-2, 0, 2]

Now skip duplicates:
nums[1]=0, nums[2]=0 (same, skip)
nums[2]=0, nums[3]=0 (same, skip)
left becomes 3, then increment to 4

3. Skipping Duplicates for Right Pointer

python
# Skip duplicates for right
while left < right and nums[right] == nums[right - 1]:
    right -= 1

right -= 1  # Move to the next different value

Same logic as left pointer, but moving in the opposite direction.

Why Skip AFTER Finding a Triplet, Not Before?

WRONG approach:

python
# This is WRONG
while left < right:
    # Skip duplicates BEFORE processing
    while left < right and nums[left] == nums[left + 1]:
        left += 1
    
    total = nums[i] + nums[left] + nums[right]
    # ...

Why it's wrong:

  • You might skip valid values before even checking if they form a triplet
  • You might miss valid triplets

CORRECT approach:

  • Process the current value
  • If it forms a valid triplet, add it
  • THEN skip duplicates

Visual Example: Step-by-Step

Input: nums = [-1, 0, 1, 2, -1, -4]

After sorting: [-4, -1, -1, 0, 1, 2]

Iteration 1: i=0, nums[i]=-4

code
[-4, -1, -1, 0, 1, 2]
  i   L              R

-4 + (-1) + 2 = -3 (too small, L++)

[-4, -1, -1, 0, 1, 2]
  i       L          R

-4 + (-1) + 2 = -3 (too small, L++)

[-4, -1, -1, 0, 1, 2]
  i           L      R

-4 + 0 + 2 = -2 (too small, L++)

... (no valid triplets)

Iteration 2: i=1, nums[i]=-1

code
[-4, -1, -1, 0, 1, 2]
      i   L          R

-1 + (-1) + 2 = 0 ✓ Found: [-1, -1, 2]

Skip duplicates:
- nums[L]=nums[L+1]? -1 == 0? No
- nums[R]=nums[R-1]? 2 == 1? No

Move both: L++, R--

[-4, -1, -1, 0, 1, 2]
      i       L  R

-1 + 0 + 1 = 0 ✓ Found: [-1, 0, 1]

Skip duplicates:
- nums[L]=nums[L+1]? 0 == 1? No
- nums[R]=nums[R-1]? 1 == 0? No

Move both: L++, R--

L >= R, stop

Iteration 3: i=2, nums[i]=-1

code
Check: i > 0 and nums[i] == nums[i-1]?
       2 > 0 and -1 == -1? YES

SKIP this iteration (avoid duplicate triplets)

Iteration 4: i=3, nums[i]=0

code
[-4, -1, -1, 0, 1, 2]
              i  L  R

0 + 1 + 2 = 3 (too large, R--)

L >= R, stop

Final result: [[-1, -1, 2], [-1, 0, 1]] ✓ No duplicates!

Common Mistakes and How to Avoid Them

Mistake 1: Using a Set to Deduplicate

python
# INEFFICIENT
result = set()
# ... find triplets ...
result.add(tuple([nums[i], nums[left], nums[right]]))
return [list(t) for t in result]

Why it's bad:

  • Requires converting lists to tuples (extra overhead)
  • Still generates duplicate triplets (wastes time)
  • Set operations are slower than skipping duplicates

Better: Skip duplicates during generation.

Mistake 2: Skipping Duplicates Before Processing

python
# WRONG
while left < right:
    while nums[left] == nums[left + 1]:  # Skip before processing
        left += 1
    # ... rest of logic

Why it's wrong: You might skip the first occurrence of a valid value.

Correct: Skip duplicates AFTER finding a triplet.

Mistake 3: Forgetting the Bounds Check

python
# WRONG - might cause IndexError
while nums[left] == nums[left + 1]:
    left += 1

Why it's wrong: When left = len(nums) - 1, nums[left + 1] is out of bounds.

Correct:

python
while left < right and nums[left] == nums[left + 1]:
    left += 1

Mistake 4: Not Moving Pointers After Skipping

python
# WRONG
if total == 0:
    result.append([nums[i], nums[left], nums[right]])
    while left < right and nums[left] == nums[left + 1]:
        left += 1
    # Forgot to increment left!

Why it's wrong: You'll process the same value again, causing an infinite loop or duplicates.

Correct:

python
while left < right and nums[left] == nums[left + 1]:
    left += 1
left += 1  # Move to next value

Complete, Correct Implementation

python
def threeSum(nums: List[int]) -> List[List[int]]:
    # Edge case: need at least 3 numbers
    if len(nums) < 3:
        return []
    
    nums.sort()
    result = []
    
    for i in range(len(nums) - 2):
        # Optimization: if smallest number is positive, no solution
        if nums[i] > 0:
            break
        
        # Skip duplicates for i
        if i > 0 and nums[i] == nums[i - 1]:
            continue
        
        left = i + 1
        right = len(nums) - 1
        
        while left < right:
            total = nums[i] + nums[left] + nums[right]
            
            if total == 0:
                result.append([nums[i], nums[left], nums[right]])
                
                # Skip duplicates for left
                while left < right and nums[left] == nums[left + 1]:
                    left += 1
                
                # Skip duplicates for right
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1
                
                # Move both pointers
                left += 1
                right -= 1
                
            elif total < 0:
                left += 1
            else:
                right -= 1
    
    return result

JavaScript Version

javascript
function threeSum(nums) {
    if (nums.length < 3) return [];
    
    nums.sort((a, b) => a - b);
    const result = [];
    
    for (let i = 0; i < nums.length - 2; i++) {
        if (nums[i] > 0) break;
        
        if (i > 0 && nums[i] === nums[i - 1]) continue;
        
        let left = i + 1;
        let right = nums.length - 1;
        
        while (left < right) {
            const total = nums[i] + nums[left] + nums[right];
            
            if (total === 0) {
                result.push([nums[i], nums[left], nums[right]]);
                
                while (left < right && nums[left] === nums[left + 1]) left++;
                while (left < right && nums[right] === nums[right - 1]) right--;
                
                left++;
                right--;
            } else if (total < 0) {
                left++;
            } else {
                right--;
            }
        }
    }
    
    return result;
}

Testing Your Implementation

Use these test cases to verify your duplicate handling:

python
# Test 1: Basic duplicates
assert threeSum([-1, 0, 1, 2, -1, -4]) == [[-1, -1, 2], [-1, 0, 1]]

# Test 2: All same value
assert threeSum([0, 0, 0, 0]) == [[0, 0, 0]]

# Test 3: Many duplicates
assert threeSum([-2, 0, 0, 2, 2]) == [[-2, 0, 2]]

# Test 4: No duplicates
assert threeSum([-1, 0, 1]) == [[-1, 0, 1]]

# Test 5: No solution
assert threeSum([1, 2, 3]) == []

# Test 6: Edge case
assert threeSum([0, 0, 0]) == [[0, 0, 0]]

Time and Space Complexity

Time Complexity: O(n²)

  • Sorting: O(n log n)
  • Outer loop: O(n)
  • Inner two-pointer loop: O(n)
  • Total: O(n log n + n²) = O(n²)

Space Complexity: O(1) or O(n)

  • O(1) if we don't count the output array
  • O(n) if we count the output (worst case: all triplets are unique)

The duplicate skipping doesn't change the complexity—it just prevents generating duplicates.

Practice Strategy

To master 3Sum duplicate handling:

  1. Solve 3Sum (#15) with duplicate handling
  2. Solve 3Sum Closest (#16) - similar pattern, no duplicates needed
  3. Solve 4Sum (#18) - extends the pattern to 4 numbers
  4. Debug a broken solution - intentionally remove duplicate skipping and see what breaks
  5. Use LeetCopilot's execution trace to visualize when duplicates are skipped

FAQ

Q: Why do we skip duplicates for i but not always for left and right?

A: We always skip duplicates for i because each iteration of the outer loop should use a unique fixed value. For left and right, we only skip duplicates AFTER finding a valid triplet, because we need to explore all combinations.

Q: Can I use a set instead of skipping duplicates?

A: You can, but it's inefficient. You'll generate duplicate triplets and then filter them out, wasting time and space. Skipping during generation is faster.

Q: What if I want to return duplicate triplets?

A: Then don't skip duplicates! But the standard 3Sum problem asks for unique triplets.

Q: Why sort the array first?

A: Sorting enables the two-pointer technique and makes duplicates adjacent, which makes skipping them easy.

Q: Does this work for 4Sum or kSum?

A: Yes! The same duplicate-skipping logic applies. For 4Sum, you have two nested loops and skip duplicates for both fixed elements.

Conclusion

Handling duplicates in 3Sum is crucial for getting the right answer. The key principles:

  1. Sort the array to make duplicates adjacent
  2. Skip duplicates for the fixed element (i) by comparing with the previous value
  3. Skip duplicates for pointers AFTER finding a valid triplet
  4. Always check bounds when comparing adjacent elements
  5. Move pointers after skipping duplicates

The pattern:

python
if i > 0 and nums[i] == nums[i - 1]:
    continue  # Skip duplicate fixed element

if total == 0:
    result.append([...])
    while left < right and nums[left] == nums[left + 1]:
        left += 1
    while left < right and nums[right] == nums[right - 1]:
        right -= 1
    left += 1
    right -= 1

Master this pattern, and you'll handle duplicates correctly not just in 3Sum, but in all multi-pointer problems. For more on two pointers, see the complete guide and advanced multi-pointer techniques.

Next time you see duplicates in the input, you'll know exactly how to handle them.

Want to Practice LeetCode Smarter?

LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.

Also compatible with Edge, Brave, and Opera

Related Tutorials