Terraform is great for static infrastructure, but writing the same resource blocks over and over is a waste of time. If you’re copy-pasting code, you’re just creating more work for your future self.

These features help you keep your code DRY (Don’t Repeat Yourself) so you can spend less time typing and more time actually building things.

for_each: Stop Repeating Resources

The for_each loop is the easiest way to iterate over a map or set. Instead of writing multiple resource blocks, you define a list and let Terraform do the work.

variable "bucket_names" {
  type    = list(string)
  default = ["bucket1", "bucket2", "bucket3"]
}

resource "aws_s3_bucket" "buckets" {
  for_each = toset(var.bucket_names)
  bucket   = each.value
}

This creates three buckets with one block. It’s cleaner, and if you need to add a fourth, you just update the variable.

Dynamic Blocks: Handling Nested Lists

Dynamic blocks are for when you have repeating blocks inside a resource—like security group rules. Writing out ten separate ingress blocks is a recipe for frustration.

Traditional way:

resource "aws_security_group" "example" {
  name = "example-sg"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

The better way:

variable "ingress_rules" {
  type = list(object({
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
  }))
  default = [
    { from_port = 80, to_port = 80, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] },
    { from_port = 443, to_port = 443, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] }
  ]
}

resource "aws_security_group" "example" {
  name = "example-sg"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

Now your security group stays manageable, regardless of how many rules you add.

Conditionals: Simple Logic

Sometimes you only want a resource if a certain condition is met—like only spinning up a costly RDS instance in production.

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

resource "aws_db_instance" "example" {
  count = var.environment == "production" ? 1 : 0
  # Other parameters here
}

If it’s not production, the count is 0, and Terraform skips it. It’s an easy way to keep your AWS bill from ballooning in dev environments.

Keep it Simple

Terraform dynamics make code more maintainable, but don’t over-engineer things. If you’re building a massive recursive loop just to avoid five lines of code, you’ve probably gone too far. Be efficient, but prioritize readability. Your future self (and your coworkers) will thank you. Things can get scary modifying complex dynamic blocks, so be careful.