Combination Sum: Backtracking With Reuse
Solve Combination Sum with a clean backtracking template. Pruning, duplicate avoidance, complexity discussion, and interview script.
What you'll learn
- ✓The general backtracking template for combinatorial search
- ✓How a start index prevents duplicate combinations
- ✓Why reusable candidates change the recursion choice
- ✓How to prune by sorting and early exit
- ✓How to discuss complexity for backtracking problems
Prerequisites
- •Familiarity with [recursion](/blog/recursion-fundamentals)
- •Comfort with [arrays](/blog/arrays-introduction)
Combination Sum is the backtracking gateway problem. Master the start-index template here and a dozen other combinatorial problems become routine.
The Problem
Given a list of distinct positive integers candidates and a target integer target, return all unique combinations of candidates that sum to target. Each candidate may be reused unlimited times.
Example:
- Input:
candidates = [2,3,6,7],target = 7 - Output:
[[2,2,3],[7]]
Intuition
We want every multiset that sums to the target. To avoid generating the same multiset in different orders ([2,2,3] vs [3,2,2]), we enforce non-decreasing index order through a start parameter: at each step we may reuse the current index or move forward, but never go back. Because candidates can repeat, the recursion stays at i rather than advancing to i + 1. Sorting first lets us break out of the loop as soon as a candidate exceeds the remaining target.
Explanation with Example
candidates = [2, 3, 6, 7], target = 7.
- Start with path
[], remaining 7. - Pick 2, path
[2], remaining 5.- Pick 2, path
[2,2], remaining 3.- Pick 2, path
[2,2,2], remaining 1. Loop sees 2 > 1, break. - Pick 3, path
[2,2,3], remaining 0. Record[2,2,3].
- Pick 2, path
- Pick 3, path
[2,3], remaining 2. Loop sees 3 > 2, break.
- Pick 2, path
- Pick 3, path
[3], remaining 4.- 3 > ? Continue. Eventually no hit.
- Pick 7, path
[7], remaining 0. Record[7].
Final: [[2,2,3],[7]].
target=7, path=[]
+----------+----------+----------+
pick 2 pick 3 pick 6 pick 7
rem=5 rem=4 rem=1 rem=0
| | X FOUND [7]
pick 2 pick 3
rem=3 rem=1
| X (no candidate <= rem with i)
pick 2
rem=1
X
pick 3
rem=0
FOUND [2,2,3]
start index forbids going back, preventing duplicate combos. Code
Note the recursive call passes i, not i + 1, because the same candidate can be reused. Sorting and the c > remaining break together give a useful constant-factor prune.
def combination_sum(candidates, target):
candidates.sort()
result = []
def backtrack(start, path, remaining):
if remaining == 0:
result.append(path[:])
return
for i in range(start, len(candidates)):
c = candidates[i]
if c > remaining:
break
path.append(c)
backtrack(i, path, remaining - c)
path.pop()
backtrack(0, [], target)
return resultimport java.util.*;
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> result = new ArrayList<>();
backtrack(candidates, 0, new ArrayList<>(), target, result);
return result;
}
private void backtrack(int[] candidates, int start, List<Integer> path, int remaining, List<List<Integer>> result) {
if (remaining == 0) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length; i++) {
if (candidates[i] > remaining) break;
path.add(candidates[i]);
backtrack(candidates, i, path, remaining - candidates[i], result);
path.remove(path.size() - 1);
}
}
}#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<vector<int>> result;
vector<int> path;
backtrack(candidates, 0, path, target, result);
return result;
}
private:
void backtrack(vector<int>& candidates, int start, vector<int>& path, int remaining, vector<vector<int>>& result) {
if (remaining == 0) {
result.push_back(path);
return;
}
for (int i = start; i < (int)candidates.size(); i++) {
if (candidates[i] > remaining) break;
path.push_back(candidates[i]);
backtrack(candidates, i, path, remaining - candidates[i], result);
path.pop_back();
}
}
};Edge Cases
- Target less than smallest candidate: return empty list.
- Single candidate equal to target: returns one combination.
- Target zero: return
[[]]if the problem allows it; typical constraints exclude this. - Large targets with small candidates: solution count can explode; the start-index trick prevents duplicate exploration but not exponential growth in valid combinations.
Complexity Analysis
- Time: O(N^(T/M)) in the worst case, where N is the candidate count, T is target, M is the smallest candidate. The branching factor is N, depth bounded by T/M.
- Space: O(T/M) for the recursion stack and current path; plus output size.
Backtracking complexity is hard to make tight; explain the bound and the pruning instead. See Big-O for context.
How to Explain It in an Interview
Open with the template: “I’ll use backtracking with a start index to avoid generating the same combination in different orders.” Explain why we pass i instead of i + 1: the problem allows reuse. Mention the sort + early break as a constant-factor improvement. Walk through one branch end-to-end; interviewers care about clean state management (push and pop in symmetry).
Related Problems
- Combination Sum II (each candidate used once, duplicates allowed in input)
- Combination Sum III
- Subsets
- Permutations
Wrap up
The start-index backtracking template is one of the highest-ROI patterns to internalize. Combination Sum is the cleanest demo: distinct candidates, reusable, fixed target. Once you can write this in under three minutes, the rest of the combinatorial set is just variations on the loop condition.
Related articles
- DSA Generate Parentheses: Counting Backtrack
Solve Generate Parentheses with counting-based backtracking. Clean invariant, walkthrough, complexity discussion, and interview tips.
- DSA Letter Combinations of a Phone Number: Index Backtracking
Solve Letter Combinations of a Phone Number with backtracking. Mapping setup, recursive enumeration, complexity, and interview walkthrough.
- DSA Permutations: Backtracking With Used Array
Solve Permutations with backtracking and a used array. Compare swap-in-place vs used-array, walkthrough, complexity, and interview tips.
- DSA Subsets: The Backtracking Template Every Interview Tests
Generate all subsets of an array using backtracking and iterative bit-mask approaches. Includes complexity analysis and interview script.