Upcoming IaCConf: Building at the Intersection of AI and IaC 🤖

Register Now ➡️

How to use Terraform Distinct Function [Examples]

terraform

Working with large Terraform lists can get messy. In this guide, we walk through how to use the Terraform distinct() function with clear, practical examples.

What is Terraform distinct function?

The Terraform distinct() function removes repeated values from a list and returns a new list containing only unique elements. It preserves the original order, but any value that appears more than once is kept only the first time it occurs. This is helpful when a configuration dynamically generates lists that may include duplicates.

The distinct() function takes a single argument — the list you want to clean up. Terraform evaluates the list and produces a new one without duplicates.

Here’s an example of using it inside a resource where a list of tags is filtered to unique values:

variable "tags" {
  type    = list(string)
  default = ["production", "web", "web", "critical"]
}

locals {
  unique_tags = distinct(var.tags)
}

resource "aws_instance" "example" {
  ami           = "ami-1234567890"
  instance_type = "t3.micro"

  tags = {
    Environment = "Production"
    Role        = "Web"
    Extra       = join(",", local.unique_tags)
  }
}

In this example, distinct(var.tags) removes duplicate values from the tags variable and returns a list containing only unique entries. That list is stored in the locals block as local.unique_tags, making it reusable elsewhere in the configuration.

The Extra tag then uses join(",", local.unique_tags) to combine the cleaned list into a single comma-separated string, which can be passed as a tag value.

Example 1: Cleaning up user input before creating a security group

Imagine you have multiple modules contributing IP addresses from different environments. Some of them might repeat. By applying distinct() to these inputs, you prevent unnecessary clutter and avoid having misleading duplicates in your security group rules.

locals {
  combined_ips = [
    "192.168.1.10",
    "192.168.1.10",
    "192.168.1.11",
    var.ad_hoc_ip
  ]
}

resource "aws_security_group_rule" "ssh" {
  type              = "ingress"
  description       = "SSH access"
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
  security_group_id = aws_security_group.core.id
  cidr_blocks       = distinct(local.combined_ips)
}

In this example, Terraform removes duplicate entries and ensures that dynamic user input does not result in unnecessary rule duplication. The final list becomes stable and predictable, even if the input changes slightly. This same pattern also works with newer security group rule resources because cidr_blocks still receives the cleaned list.

Example 2: Producing a clean output variable for downstream modules

Sometimes Terraform modules must pass lists of identifiers or tags to downstream modules. If those lists come from multiple sources, it is easy to end up with duplicates.

Using distinct() in an output ensures that the consuming module receives a clean list.

locals {
  raw_tags = [
    "production",
    "production",
    "web",
    "backend",
    var.extra_tag
  ]
}

output "normalized_tags" {
  value = distinct(local.raw_tags)
}

A downstream module that consumes normalized_tags receives a list that is easier to handle and avoids repeated values in naming rules or lookups. This also helps keep plans stable because Terraform does not constantly detect changes caused by duplicates being introduced or removed.

Because the list is already cleaned up, it is less likely to create noisy diffs when tags change in different places.

Example 3: Filtering duplicates when generating dynamic block entries

Terraform dynamic blocks often iterate through lists to create repeated nested structures. If the list contains duplicates, the resulting configuration can create redundant or conflicting entries.

Applying distinct() to the list feeding the dynamic block avoids this problem and yields a more reliable plan.

locals {
  service_ports = [8080, 8080, 9090, 9443]
}

resource "kubernetes_service" "app" {
  metadata {
    name = "myapp"
  }

  spec {
    selector = {
      app = "myapp"
    }

    dynamic "port" {
      for_each = distinct(local.service_ports)
      content {
        port        = port.value
        target_port = port.value
      }
    }
  }
}

Here, the Kubernetes service ends up with unique port mappings even though the initial list contained duplicated port numbers. This prevents unnecessary configuration drift and keeps the resource definition easy to read. In simple terms, distinct() here just stops Terraform from creating the same port block more than once.

How to combine distinct with flatten in Terraform

The combination of distinct() and flatten() is very common in Terraform when you have a list of lists and you want to turn it into a single, clean list with no duplicates.

The flatten() function merges nested lists into one continuous list. The distinct() function then removes repeated values from that merged list. Using them together gives you a predictable final list that behaves well when passed into resources, modules, or output variables.

The pattern normally looks like this:

distinct(flatten(list_of_lists))

Terraform evaluates the innermost function first. It flattens the nested list structure into a regular list. After that, it applies distinct() to remove duplicates. This helps keep plans stable, avoids noisy diffs, and ensures your configuration handles dynamic inputs gracefully.

The following example shows two modules, each generating its own list of CIDR blocks. Instead of handling them separately, we combine them into a single value using flatten() and then clean them with distinct().

locals {
  cidr_collections = [
    ["10.0.0.0/24", "10.0.1.0/24"],
    ["10.0.1.0/24", "10.0.2.0/24"],
    var.extra_cidrs
  ]
}

locals {
  normalized_cidrs = distinct(flatten(local.cidr_collections))
}

resource "aws_security_group" "merged" {
  name        = "merged-sg"
  description = "Example showing flatten and distinct"
  vpc_id      = "vpc-123456"

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = local.normalized_cidrs
  }
}

Terraform takes the nested lists inside cidr_collections and merges them into a single list. Duplicates such as 10.0.1.0/24 are removed by distinct(). The result is a tidy set of CIDR entries that makes the security group both clean and stable.

Key points

In Terraform, distinct() is a built-in function that takes a list and removes duplicate elements, keeping only the first occurrence of each value and preserving the original order.

Terraform is really powerful, but for an end to end secure GitOps approach most teams use a product that can run and govern their Terraform or OpenTofu workflows, like Spacelift. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:

  • Policies (based on Open Policy Agent)
  • Multi-IaC workflows
  • Self-service infrastructure
  • Integrations with any third-party tools

If you want to learn more about Spacelift, create a free account today or book a demo with one of our engineers.

Note: New versions of Terraform are placed under the Business Source License (BUSL), but everything created before version 1.5.x remains open source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.

Discover better way to manage Terraform

Spacelift helps manage Terraform state, build more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization and many more.

Learn more

Terraform Functions Cheat Sheet

Grab our ultimate cheat sheet PDF for all
the Terraform functions you need on hand.

Share your data and download the cheat sheet