---
sidebar_label: setproduct
description: |-
  The setproduct function finds all of the possible combinations of elements
  from all of the given sets by computing the cartesian product.
---

# `setproduct` Function

The `setproduct` function finds all of the possible combinations of elements
from all of the given sets by computing the
[Cartesian product](https://en.wikipedia.org/wiki/Cartesian_product).

```hcl
setproduct(sets...)
```

This function is particularly useful for finding the exhaustive set of all
combinations of members of multiple sets, such as per-application-per-environment
resources.

```
> setproduct(["development", "staging", "production"], ["app1", "app2"])
[
  [
    "development",
    "app1",
  ],
  [
    "development",
    "app2",
  ],
  [
    "staging",
    "app1",
  ],
  [
    "staging",
    "app2",
  ],
  [
    "production",
    "app1",
  ],
  [
    "production",
    "app2",
  ],
]
```

You must pass at least two arguments to this function.

Although defined primarily for sets, this function can also work with lists.
If all of the given arguments are lists then the result is a list, preserving
the ordering of the given lists. Otherwise the result is a set. In either case,
the result's element type is a list of values corresponding to each given
argument in turn.

## Examples

There is an example of the common usage of this function above. There are some
other situations that are less common when hand-writing but may arise in
reusable module situations.

If any of the arguments is empty then the result is always empty itself,
similar to how multiplying any number by zero gives zero:

```
> setproduct(["development", "staging", "production"], [])
[]
```

Similarly, if all of the arguments have only one element then the result has
only one element, which is the first element of each argument:

```
> setproduct(["a"], ["b"])
[
  [
    "a",
    "b",
  ],
]
```

Each argument must have a consistent type for all of its elements. If not,
OpenTofu will attempt to convert to the most general type, or produce an
error if such a conversion is impossible. For example, mixing both strings and
numbers results in the numbers being converted to strings so that the result
elements all have a consistent type:

```
> setproduct(["staging", "production"], ["a", 2])
[
  [
    "staging",
    "a",
  ],
  [
    "staging",
    "2",
  ],
  [
    "production",
    "a",
  ],
  [
    "production",
    "2",
  ],
]
```

## Finding combinations for `for_each`

The
[resource `for_each`](../../language/meta-arguments/for_each.mdx)
and
[`dynamic` block](../../language/expressions/dynamic-blocks.mdx)
language features both require a collection value that has one element for
each repetition.

Sometimes your input data comes in separate values that cannot be directly
used in a `for_each` argument, and `setproduct` can be a useful helper function
for the situation where you want to find all unique combinations of elements in
a number of different collections.

For example, consider a module that declares variables like the following:

```hcl
variable "networks" {
  type = map(object({
    base_cidr_block = string
  }))
}

variable "subnets" {
  type = map(object({
    number = number
  }))
}
```

If the goal is to create each of the defined subnets per each of the defined networks, creating the top-level networks can directly use `var.networks` because it is already in a form where the resulting instances match one-to-one with map elements:

```hcl
resource "aws_vpc" "example" {
  for_each = var.networks

  cidr_block = each.value.base_cidr_block
}
```

However, to declare all of the _subnets_ with a single `resource` block, you must first produce a collection whose elements represent all of the combinations of networks and subnets, so that each element itself represents a subnet:

```hcl
locals {
  # setproduct works with sets and lists, but the variables are both maps
  # so convert them first.
  networks = [
    for key, network in var.networks : {
      key        = key
      cidr_block = network.cidr_block
    }
  ]
  subnets = [
    for key, subnet in var.subnets : {
      key    = key
      number = subnet.number
    }
  ]

  network_subnets = [
    # in pair, element zero is a network and element one is a subnet,
    # in all unique combinations.
    for pair in setproduct(local.networks, local.subnets) : {
      network_key = pair[0].key
      subnet_key  = pair[1].key
      network_id  = aws_vpc.example[pair[0].key].id

      # The cidr_block is derived from the corresponding network. Refer to the
      # cidrsubnet function for more information on how this calculation works.
      cidr_block = cidrsubnet(pair[0].cidr_block, 4, pair[1].number)
    }
  ]
}

resource "aws_subnet" "example" {
  # local.network_subnets is a list, so project it into a map
  # where each key is unique. Combine the network and subnet keys to
  # produce a single unique key per instance.
  for_each = {
    for subnet in local.network_subnets : "${subnet.network_key}.${subnet.subnet_key}" => subnet
  }

  vpc_id            = each.value.network_id
  availability_zone = each.value.subnet_key
  cidr_block        = each.value.cidr_block
}
```

The `network_subnets` list in the example above creates one subnet instance per combination of network and subnet elements in the input variables. So for this example input:

```hcl
networks = {
  a = {
    base_cidr_block = "10.1.0.0/16"
  }
  b = {
    base_cidr_block = "10.2.0.0/16"
  }
}
subnets = {
  a = {
    number = 1
  }
  b = {
    number = 2
  }
  c = {
    number = 3
  }
}
```

The `network_subnets` output would look similar to the following:

```hcl
[
  {
    "cidr_block" = "10.1.16.0/20"
    "network_id" = "vpc-0bfb00ca6173ea5aa"
    "network_key" = "a"
    "subnet_key" = "a"
  },
  {
    "cidr_block" = "10.1.32.0/20"
    "network_id" = "vpc-0bfb00ca6173ea5aa"
    "network_key" = "a"
    "subnet_key" = "b"
  },
  {
    "cidr_block" = "10.1.48.0/20"
    "network_id" = "vpc-0bfb00ca6173ea5aa"
    "network_key" = "a"
    "subnet_key" = "c"
  },
  {
    "cidr_block" = "10.2.16.0/20"
    "network_id" = "vpc-0d193e011f6211a7d"
    "network_key" = "b"
    "subnet_key" = "a"
  },
  {
    "cidr_block" = "10.2.32.0/20"
    "network_id" = "vpc-0d193e011f6211a7d"
    "network_key" = "b"
    "subnet_key" = "b"
  },
  {
    "cidr_block" = "10.2.48.0/20"
    "network_id" = "vpc-0d193e011f6211a7d"
    "network_key" = "b"
    "subnet_key" = "c"
  },
]
```

## Related Functions

- [`contains`](../../language/functions/contains.mdx) tests whether a given list or set contains
  a given element value.
- [`flatten`](../../language/functions/flatten.mdx) is useful for flattening hierarchical data
  into a single list, for situations where the relationships between two
  object types are defined explicitly.
- [`setintersection`](../../language/functions/setintersection.mdx) computes the _intersection_ of
  multiple sets.
- [`setsubtract`](../../language/functions/setsubtract.mdx) computes the _relative complement_ of two sets
- [`setunion`](../../language/functions/setunion.mdx) computes the _union_ of multiple
  sets.
