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
whileloops 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)
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 resultWhat Goes Wrong
Input: nums = [-1, 0, 1, 2, -1, -4]
After sorting: [-4, -1, -1, 0, 1, 2]
Execution trace:
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: stopResult: [[-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:
- When choosing the fixed element (i)
- After finding a valid triplet (left pointer)
- After finding a valid triplet (right pointer)
The Correct Implementation
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 resultBreaking Down the Duplicate Skipping Logic
1. Skipping Duplicates for the Fixed Element (i)
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]:
continueWhy i > 0?
- We need to compare
nums[i]withnums[i-1] - When
i=0, there's nonums[-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:
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], SKIP2. Skipping Duplicates for Left Pointer
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 valueWhy 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:
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 43. Skipping Duplicates for Right Pointer
# Skip duplicates for right
while left < right and nums[right] == nums[right - 1]:
right -= 1
right -= 1 # Move to the next different valueSame logic as left pointer, but moving in the opposite direction.
Why Skip AFTER Finding a Triplet, Not Before?
WRONG approach:
# 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
[-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
[-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, stopIteration 3: i=2, nums[i]=-1
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
[-4, -1, -1, 0, 1, 2]
i L R
0 + 1 + 2 = 3 (too large, R--)
L >= R, stopFinal result: [[-1, -1, 2], [-1, 0, 1]] ✓ No duplicates!
Common Mistakes and How to Avoid Them
Mistake 1: Using a Set to Deduplicate
# 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
# WRONG
while left < right:
while nums[left] == nums[left + 1]: # Skip before processing
left += 1
# ... rest of logicWhy 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
# WRONG - might cause IndexError
while nums[left] == nums[left + 1]:
left += 1Why it's wrong: When left = len(nums) - 1, nums[left + 1] is out of bounds.
Correct:
while left < right and nums[left] == nums[left + 1]:
left += 1Mistake 4: Not Moving Pointers After Skipping
# 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:
while left < right and nums[left] == nums[left + 1]:
left += 1
left += 1 # Move to next valueComplete, Correct Implementation
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 resultJavaScript Version
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:
# 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:
- Solve 3Sum (#15) with duplicate handling
- Solve 3Sum Closest (#16) - similar pattern, no duplicates needed
- Solve 4Sum (#18) - extends the pattern to 4 numbers
- Debug a broken solution - intentionally remove duplicate skipping and see what breaks
- 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:
- Sort the array to make duplicates adjacent
- Skip duplicates for the fixed element (
i) by comparing with the previous value - Skip duplicates for pointers AFTER finding a valid triplet
- Always check bounds when comparing adjacent elements
- Move pointers after skipping duplicates
The pattern:
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 -= 1Master 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
