DEV Community

Mary Mutua
Mary Mutua

Posted on

Mastering Loops and Conditionals in Terraform

Day 10 of my Terraform journey was all about writing less repetitive infrastructure code.

Up to this point, most resources had been declared one by one. That works for small labs, but it becomes painful fast when you need multiple IAM users, repeated rules, or environment-specific behavior.

Today I learned the four Terraform tools that make configurations dynamic:

  • count
  • for_each
  • for expressions
  • ternary conditionals

This was one of the most practical Terraform topics so far.

Why This Matters

Terraform is declarative, but these features make it feel much closer to a programming language.

They help you:

  • reduce repetition
  • create multiple resources safely
  • transform data cleanly
  • make infrastructure behavior change by environment

They are also heavily tested in the Terraform Associate exam.

1. Loops with count

count is the simplest loop in Terraform.

Use it when you want Terraform to create a fixed number of similar resources.

resource "aws_iam_user" "example" {
  count = 3
  name  = "user-${count.index}"
}
Enter fullscreen mode Exit fullscreen mode

This creates:

  • user-0
  • user-1
  • user-2

The important part is count.index, which gives each resource its position.

The count Index Problem

count becomes risky when it is tied to a list that can change.

variable "user_names" {
  type    = list(string)
  default = ["alice", "bob", "charlie"]
}

resource "aws_iam_user" "example" {
  count = length(var.user_names)
  name  = var.user_names[count.index]
}
Enter fullscreen mode Exit fullscreen mode

At first, Terraform maps the list like this:

  • index 0alice
  • index 1bob
  • index 2charlie

Now imagine I remove alice from the list:

default = ["bob", "charlie"]
Enter fullscreen mode Exit fullscreen mode

Terraform now sees:

  • index 0bob
  • index 1charlie

The configuration still runs, but the identities shift. Terraform tracks these resources by position, so when the list changes, later resources can be updated or recreated unexpectedly.

That is why count can be destructive when list order changes.

2. Loops with for_each

for_each solves that problem by using keys or values as identity instead of numeric positions.

variable "user_names" {
  type    = set(string)
  default = ["alice", "bob", "charlie"]
}

resource "aws_iam_user" "example" {
  for_each = var.user_names
  name     = each.value
}
Enter fullscreen mode Exit fullscreen mode

Now Terraform tracks users by value, not by index.

If I remove alice, only alice is affected.

bob and charlie keep their identities.

That makes for_each much safer for collections that may change over time.

for_each with a Map

for_each becomes even more useful when paired with a map.

variable "users" {
  type = map(object({
    department = string
    admin      = bool
  }))
  default = {
    alice = { department = "engineering", admin = true }
    bob   = { department = "marketing", admin = false }
  }
}

resource "aws_iam_user" "example" {
  for_each = var.users
  name     = each.key

  tags = {
    Department = each.value.department
  }
}
Enter fullscreen mode Exit fullscreen mode

This lets each item carry extra configuration.

3. for Expressions

for expressions do not create resources. They transform data.

For example, this outputs uppercase names:

output "upper_names" {
  value = [for name in var.user_names : upper(name)]
}
Enter fullscreen mode Exit fullscreen mode

And this creates a map of usernames to ARNs:

output "user_arns" {
  value = { for name, user in aws_iam_user.example : name => user.arn }
}
Enter fullscreen mode Exit fullscreen mode

A good way to think about it is:

  • count and for_each create resources
  • for expressions reshape data

4. Ternary Conditionals

Terraform conditionals use this form:

condition ? true_value : false_value
Enter fullscreen mode Exit fullscreen mode

They are useful when you want infrastructure behavior to change based on input.

Example:

variable "environment" {
  type    = string
  default = "dev"
}

locals {
  instance_type = var.environment == "production" ? "t2.medium" : "t2.micro"
}
Enter fullscreen mode Exit fullscreen mode

This means:

  • if environment is production, use t2.medium
  • otherwise use t2.micro

Then you can use it like this:

resource "aws_instance" "web" {
  instance_type = local.instance_type
}
Enter fullscreen mode Exit fullscreen mode

Conditionals with count

Conditionals also work well with count when you want a resource to be optional.

variable "enable_autoscaling" {
  description = "Enable autoscaling for the cluster"
  type        = bool
  default     = true
}

resource "aws_autoscaling_policy" "scale_out" {
  count = var.enable_autoscaling ? 1 : 0
}
Enter fullscreen mode Exit fullscreen mode

This means:

  • if enable_autoscaling = true, create the policy
  • if enable_autoscaling = false, skip it

When to Use count vs for_each

This was the biggest lesson of the day.

Use count when:

  • you need a fixed number of nearly identical resources
  • order does not matter
  • the collection is unlikely to change

Use for_each when:

  • items have stable names or keys
  • the collection may change over time
  • each item may have different configuration

In practice, for_each is often safer.

My Main Takeaway

Day 10 made Terraform feel much more dynamic.

The most important lesson for me was this:

  • count is simple
  • for_each is safer
  • for expressions clean up data
  • conditionals make infrastructure flexible

Together, these four tools make Terraform much more powerful.

Full Code

👉 Github LInk

Follow My Journey
This is Day 10 of my 30-Day Terraform Challenge.

See you on Day 11 🚀

Top comments (0)