Skip to main content

15. 3Sum

mediumAsked at Shopify

3Sum is Shopify's classic two-pointer follow-up to Two Sum. The interviewer wants you to sort, fix one element, and two-pointer the remainder — and to handle duplicate-skipping cleanly.

By Alex Chen, Founder, InterviewChamp.AI · Last verified

Source citations

Public interview reports confirming this problem appears in Shopify loops.

  • Glassdoor (2026-Q1)Shopify Backend Developer + Senior Engineering onsites cite 3Sum as a frequent follow-up to Two Sum.
  • Reddit r/cscareerquestions (2025-11)Shopify new-grad reports include this as the medium-round after Two Sum.

Problem

Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0. Notice that the solution set must not contain duplicate triplets.

Constraints

  • 3 <= nums.length <= 3000
  • -10^5 <= nums[i] <= 10^5

Examples

Example 1

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

Example 2

Input
nums = [0,1,1]
Output
[]

Example 3

Input
nums = [0,0,0]
Output
[[0,0,0]]

Approaches

1. Brute-force triple loop with Set dedup

Three nested loops; for each (i, j, k) summing to 0, add the sorted triplet to a Set.

Time
O(n^3)
Space
O(n^3) worst case for the Set
function threeSumBrute(nums) {
  const set = new Set();
  const out = [];
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      for (let k = j + 1; k < nums.length; k++) {
        if (nums[i] + nums[j] + nums[k] === 0) {
          const t = [nums[i], nums[j], nums[k]].sort((a, b) => a - b);
          const key = t.join(',');
          if (!set.has(key)) { set.add(key); out.push(t); }
        }
      }
    }
  }
  return out;
}

Tradeoff: Cubic; barely passes for n = 3000. Use only as the warmup explanation.

2. Sort + two-pointer (canonical optimal)

Sort the array. For each i, two-pointer (left, right) the suffix looking for nums[i] + nums[left] + nums[right] = 0. Skip duplicates at every level.

Time
O(n^2)
Space
O(1) excluding output
function threeSum(nums) {
  nums.sort((a, b) => a - b);
  const out = [];
  for (let i = 0; i < nums.length - 2; i++) {
    if (i > 0 && nums[i] === nums[i - 1]) continue;
    if (nums[i] > 0) break;
    let left = i + 1, right = nums.length - 1;
    while (left < right) {
      const sum = nums[i] + nums[left] + nums[right];
      if (sum === 0) {
        out.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 (sum < 0) {
        left++;
      } else {
        right--;
      }
    }
  }
  return out;
}

Tradeoff: O(n^2) which is what Shopify expects. The `if (nums[i] > 0) break` short-circuit cuts work when the array is mostly positives. The duplicate-skipping logic at all three levels is the hard part — practice it until it's automatic.

3. Hash set 'two-sum on the suffix' alternative

Sort first, then for each i, do a hash-set-based two-sum on nums[i+1..end] looking for target = -nums[i].

Time
O(n^2)
Space
O(n)
function threeSumHash(nums) {
  nums.sort((a, b) => a - b);
  const out = [];
  for (let i = 0; i < nums.length - 2; i++) {
    if (i > 0 && nums[i] === nums[i - 1]) continue;
    const seen = new Set();
    let j = i + 1;
    while (j < nums.length) {
      const need = -nums[i] - nums[j];
      if (seen.has(need)) {
        out.push([nums[i], need, nums[j]]);
        while (j + 1 < nums.length && nums[j] === nums[j + 1]) j++;
      }
      seen.add(nums[j]);
      j++;
    }
  }
  return out;
}

Tradeoff: Same O(n^2) but uses extra O(n) memory per i. Two-pointer is cleaner for the duplicate-skipping. Bring it up only if the interviewer prefers hash maps.

Shopify-specific tips

Shopify's grading bar: explicit handling of duplicates at all THREE pointer levels (i, left, right). Most candidates fail by skipping duplicates only at the outer level. The expected follow-up is 3Sum Closest (LeetCode 16) or 4Sum (LeetCode 18) — both extend the same sort + two-pointer template.

Common mistakes

  • Forgetting `nums[i] > 0 -> break` — minor perf opt but interviewers notice.
  • Skipping duplicates only at the i level; need to skip at left and right after finding a triplet too.
  • Comparing `nums[left] === nums[left - 1]` (back-check) which fails when left points just inside the current window — use the forward-check pattern shown.
  • Returning indices instead of values (the problem asks for values; common confusion with Two Sum).

Follow-up questions

An interviewer at Shopify may pivot to one of these next:

  • 3Sum Closest (LeetCode 16) — find triplet with sum closest to target.
  • 4Sum (LeetCode 18) — extend to four numbers.
  • 3Sum Smaller (LeetCode 259) — count triplets with sum < target.
  • kSum — generalize to k numbers (recursion + base case as Two Sum).
  • What if duplicates are allowed in the output (a triplet can appear once per distinct ordering)?

Solve it now

Free. No sign-up. Python and JavaScript run instantly in your browser.

Output

Press Run or Cmd+Enter to execute

FAQ

Why does sort + two-pointer beat hash maps here?

Both are O(n^2). Two-pointer uses O(1) extra space (no inner hash); the duplicate-skipping is cleaner because sorting clusters duplicates together. Shopify expects two-pointer as the default.

Can you do better than O(n^2)?

Not in the comparison-based model — 3Sum has a known matching lower bound. For specialized inputs (integers in a small range) you can do O(n^2 / log n) via fancy tricks, but they're not interview-grade.

Free learning resources

Curated free links for this problem.

Practice these live with InterviewChamp.AI

Drill 3Sum and other Shopify interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.

Practice these live with InterviewChamp.AI →