JavaScript
Combinations
Cartesian Product
Arrays
Programming

Finding All Combinations Cartesian product of JavaScript array values

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The Cartesian product of arrays means taking one value from each array and producing every possible combination. It is useful for generating filter combinations, test cases, SKU variants, and other cross-product style inputs.

The implementation is not hard, but there are two details that matter in practice: the shape of the result and how quickly the number of combinations grows.

What the Result Looks Like

Given these arrays:

javascript
const colors = ["red", "blue"];
const sizes = ["S", "M"];
const styles = ["v-neck", "crew"];

The Cartesian product is:

javascript
1[
2  ["red", "S", "v-neck"],
3  ["red", "S", "crew"],
4  ["red", "M", "v-neck"],
5  ["red", "M", "crew"],
6  ["blue", "S", "v-neck"],
7  ["blue", "S", "crew"],
8  ["blue", "M", "v-neck"],
9  ["blue", "M", "crew"],
10]

Each output row contains one element from each input array, in order.

A Clean reduce Implementation

The most readable JavaScript solution is often a reduce that expands combinations one array at a time:

javascript
1function cartesianProduct(arrays) {
2  return arrays.reduce(
3    (accumulator, currentArray) => {
4      const next = [];
5
6      for (const existing of accumulator) {
7        for (const value of currentArray) {
8          next.push([...existing, value]);
9        }
10      }
11
12      return next;
13    },
14    [[]]
15  );
16}
17
18const result = cartesianProduct([
19  ["red", "blue"],
20  ["S", "M"],
21  ["v-neck", "crew"],
22]);
23
24console.log(result);

The starting value [[]] is important. It represents one empty combination before any arrays have been processed. Each step appends one more dimension.

Why This Works

At each iteration:

  • 'accumulator holds the combinations built so far'
  • 'currentArray holds the next dimension to combine'
  • the nested loops append every value from currentArray to every existing combination

That means the algorithm grows the result layer by layer instead of trying to solve the whole problem at once.

A Recursive Version

If you prefer recursion, the same idea can be expressed this way:

javascript
1function cartesianRecursive(arrays, index = 0) {
2  if (index === arrays.length) {
3    return [[]];
4  }
5
6  const tailProduct = cartesianRecursive(arrays, index + 1);
7  const result = [];
8
9  for (const value of arrays[index]) {
10    for (const tail of tailProduct) {
11      result.push([value, ...tail]);
12    }
13  }
14
15  return result;
16}
17
18console.log(cartesianRecursive([["A", "B"], [1, 2], [true, false]]));

This is elegant, but the reduce version is often easier for teams to maintain because it keeps all logic in one place.

Handling Edge Cases

Two edge cases matter:

If the outer input is empty, many implementations return [[]], meaning "one empty combination." That is mathematically consistent, though some applications may prefer [].

If any inner array is empty, the total product should be empty because you cannot choose one element from each array when one dimension has no values.

The earlier reduce solution already handles both cases correctly.

Growth Can Explode Quickly

The total number of combinations is the product of the input lengths. With arrays of lengths 3, 4, and 5, the output size is 60. With five arrays of length 10, the output size is 100000.

That means the real challenge is often not syntax but scale. If you only need to iterate combinations one at a time, a generator-style approach can avoid building everything up front. But for many application-level tasks, returning an array of arrays is fine.

Mapping Results into Objects

In business code, you often want labeled combinations rather than raw tuples. You can map the result after generation:

javascript
1const keys = ["color", "size", "style"];
2const combinations = cartesianProduct([
3  ["red", "blue"],
4  ["S", "M"],
5  ["v-neck", "crew"],
6]);
7
8const objects = combinations.map(values =>
9  Object.fromEntries(keys.map((key, index) => [key, values[index]]))
10);
11
12console.log(objects);

That is useful for product variants, test parameter grids, and form scenario generation.

Common Pitfalls

The most common mistake is confusing combinations with permutations. Cartesian products choose one item from each input array. They do not reorder items within a single array.

Another issue is underestimating output size. The result count grows multiplicatively, so a few extra dimensions can create a huge array.

Developers also sometimes start with [] instead of [[]] in the reducer. That produces no results because there is no base combination to extend.

Finally, be clear about the output format. Some callers expect arrays, while others expect joined strings or keyed objects.

Summary

  • The Cartesian product returns every way to pick one value from each input array.
  • A reduce-based solution is concise and works well for most JavaScript use cases.
  • The base accumulator should start as [[]].
  • Result size grows as the product of the input lengths.
  • Convert the output into objects when the combinations need meaningful field names.

Course illustration
Course illustration

All Rights Reserved.