Devops

Terraform Type Constraints: Working Effectively with set

April 7, 2026
Published
#DevOps#HCL#Infrastructure as Code#Terraform#Type Constraints

Terraform’s type system looks simple at first glance—strings, numbers, lists—but it gets interesting once you start modeling real infrastructure inputs. One type that trips up even experienced users is set.

It behaves like a list, but with strict rules around uniqueness and ordering. If you’ve ever had Terraform “shuffle” your values or throw odd diffs, chances are a set was involved.

What is a set in Terraform?

A set is an unordered collection of unique values. Unlike lists, sets do not preserve order and automatically remove duplicates.

Basic example:

TEXT
1variable "allowed_ips" {
2  type = set(string)
3}
4

If a user provides:

TEXT
1allowed_ips = [
2  "192.168.1.1",
3  "192.168.1.2",
4  "192.168.1.1"
5]
6

Terraform will internally treat it as:

TEXT
1{
2  "192.168.1.1",
3  "192.168.1.2"
4}
5

Duplicate values are removed automatically.

Why use a set instead of a list?

Here’s where things get practical.

  • You don’t care about order
  • You want automatic deduplication
  • You need stable comparisons regardless of input order

A common use case is security rules, tags, or identifiers where order has no semantic meaning.

Example: Security group rules

TEXT
1variable "open_ports" {
2  type = set(number)
3  default = [22, 80, 443, 80]
4}
5

This ensures you never accidentally create duplicate rules.

Here’s where things get tricky: Ordering

Sets are unordered. That means:

  • Terraform may display values in a different order than you defined
  • Plan output can look confusing
  • You cannot rely on index-based access

This will not work as expected:

TEXT
1var.allowed_ips[0]

Because sets don’t guarantee position.

If ordering matters, use a list instead.

Converting between list and set

You’ll often need to switch between types. Terraform provides helper functions.

Convert list to set

TEXT
1locals {
2  unique_ips = toset(var.allowed_ips)
3}

Convert set to list

TEXT
1locals {
2  ip_list = tolist(var.allowed_ips)
3}

Be careful: converting a set to a list introduces non-deterministic ordering.

Using set with objects

Sets aren’t limited to primitive types. You can define sets of complex objects.

TEXT
1variable "users" {
2  type = set(object({
3    name = string
4    role = string
5  }))
6}

Terraform determines uniqueness based on the entire object structure.

Example input:

TEXT
1users = [
2  { name = "alice", role = "admin" },
3  { name = "bob", role = "dev" },
4  { name = "alice", role = "admin" }
5]
6

The duplicate object will be removed.

Subtle gotcha

If two objects differ even slightly, Terraform treats them as distinct:

TEXT
1{ name = "alice", role = "admin" }
2{ name = "alice", role = "Admin" }

These are considered different due to case sensitivity.

Using sets with for_each

Sets work well with for_each, but there’s a constraint: Terraform needs stable keys.

This works:

TEXT
1resource "aws_security_group_rule" "example" {
2  for_each = var.allowed_ips
3
4  cidr_blocks = [each.value]
5  from_port   = 80
6  to_port     = 80
7  protocol    = "tcp"
8}

Each value becomes its own instance.

But with complex objects, you may need to transform the set into a map:

JAVASCRIPT
1locals {
2  user_map = {
3    for user in var.users : user.name => user
4  }
5}
6

This ensures predictable keys.

A common mistake developers make

Using a set when order actually matters.

Example scenario:

  • Provisioning subnets in a specific sequence
  • Assigning priorities
  • Index-based resource references

In these cases, a list is the correct choice. Using a set will eventually lead to confusing diffs or broken assumptions.

Validation with set types

You can enforce constraints on sets using validation blocks.

JSON
1variable "allowed_regions" {
2  type = set(string)
3
4  validation {
5    condition = alltrue([
6      for region in var.allowed_regions :
7      contains(["us-east-1", "us-west-1"], region)
8    ])
9    error_message = "Only approved regions are allowed."
10  }
11}

This keeps your infrastructure inputs clean and predictable.

When should you reach for set?

Use a set when:

  • Duplicates must be eliminated automatically
  • Order is irrelevant
  • You want consistent comparisons across runs

Avoid it when:

  • You rely on ordering
  • You need index access
  • You expect stable output formatting

Final thought

The set type in Terraform is deceptively simple. It’s powerful for enforcing uniqueness and simplifying input handling, but it can introduce subtle bugs if you assume list-like behavior.

Once you internalize that sets are unordered and deduplicated by design, they become a reliable tool in your Terraform toolkit—especially for modeling real-world infrastructure constraints.

Comments

Leave a comment on this article with your name, email, and message.

Loading comments...

Similar Articles

More posts from the same category you may want to read next.

Share: