Is Using Terraform Count Ever a Good Idea? A Deep Dive into the HashiCorp Terraform Source Code
Using terraform count is a good idea when you need to create a fixed number of homogeneous resources with sequential indexing, but you should avoid it when working with dynamic collections or computed values that could trigger unnecessary resource recreation.
The count meta-argument in Terraform has sparked debate among infrastructure engineers about when it is appropriate to use versus alternatives like for_each. According to the HashiCorp Terraform source code, count is implemented through specific graph transformers that expand resources into multiple instances during the planning phase. Understanding how terraform count works under the hood—specifically through the ResourceCountTransformer and OrphanCountTransformer—helps you decide when this approach improves your infrastructure code and when it risks creating destructive side effects.
How Terraform Count Works Under the Hood
When Terraform encounters a resource or module containing a count meta-argument, the ResourceCountTransformer expands the single configuration node into N distinct vertices in the planning graph, where N represents the evaluated count value. This expansion occurs early in the plan phase, before the graph undergoes dependency analysis.
The ResourceCountTransformer
The ResourceCountTransformer in internal/terraform/transform_resource_count.go performs the critical work of duplicating resource configurations. For each index from 0 to count-1, it creates a separate graph vertex and injects the count.index value into the evaluation context. Any references within the resource that use count.index are rewired to point to the appropriate instance.
Orphan Detection and State Management
When the count value decreases between runs, Terraform must identify which instances no longer exist in the new configuration. The OrphanCountTransformer in internal/terraform/transform_orphan_count.go detects these orphaned instances—those present in state but outside the new count range—and marks them for destruction.
Additionally, internal/terraform/validate_selfref.go prevents self-referential expressions in count arguments that could create circular dependencies during graph construction.
When to Use Terraform Count
Using terraform count is appropriate in specific scenarios where the number of instances is stable and predictable:
| Scenario | Rationale |
|---|---|
| Fixed numeric replication | Creating a constant number of homogeneous resources (e.g., "create exactly 3 subnets") where the count expression is a constant or simple variable. |
| User-controlled sizing | Driving creation from a numeric variable with validation (e.g., variable "node_count"), ensuring changes are intentional and explicit. |
| Sequential indexing requirements | When you need count.index for deterministic naming or numeric IDs (e.g., name = "worker-${count.index}"). Unlike for_each, count provides a guaranteed sequential integer starting at 0. |
| Module replication | Repeating a module block a fixed number of times where each instance requires isolation (e.g., creating multiple VPCs with identical configurations). |
When to Avoid Terraform Count
Despite its utility, terraform count creates risks in several situations:
Prefer for_each for Dynamic Collections
Use for_each instead of count when iterating over a set, map, or list of identifiers. The for_each meta-argument, handled in internal/terraform/transform_variable.go, uses the actual map key or set element as the instance identifier rather than a positional index.
This distinction is critical: if you remove an item from the middle of a list used with count, Terraform will detect that every subsequent resource has changed its index, potentially destroying and recreating all downstream instances. With for_each, removing an item only affects that specific resource.
Avoid Computed Values in Count Expressions
Never set count based on computed values that can change during the same plan phase, such as length(data.aws_availability_zones.available.names). This creates count drift—the plan may see a different number of instances than the state expects.
When count decreases, the OrphanCountTransformer marks excess instances for destruction. When count increases based on computed data, Terraform may attempt to create resources before the data source is fully resolved, leading to errors or unnecessary replacements.
Terraform Count Examples: Best and Worst Practices
Good Practice: Constant Count with Validation
variable "subnet_count" {
type = number
default = 3
validation {
condition = var.subnet_count >= 1 && var.subnet_count <= 5
error_message = "Subnet count must be between 1 and 5."
}
}
resource "aws_subnet" "example" {
count = var.subnet_count
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = element(var.azs, count.index)
tags = {
Name = "example-subnet-${count.index}"
}
}
This approach works because the count value is a validated variable, ensuring changes are intentional. The ResourceCountTransformer in internal/terraform/transform_resource_count.go creates three separate aws_subnet vertices, each with its own index for deterministic CIDR calculation and naming.
Good Practice: Module Replication
variable "node_count" {
description = "Number of compute nodes"
type = number
default = 2
}
module "worker" {
source = "./modules/worker"
count = var.node_count
name = "worker-${count.index}"
region = var.region
}
Module blocks support count just like resources. The core processes module expansion through the same graph transformation logic, creating isolated module instances with separate state namespaces.
Better Alternative: Using for_each for Dynamic Collections
variable "subnet_ids" {
type = list(string)
}
resource "aws_route_table_association" "example" {
for_each = toset(var.subnet_ids)
subnet_id = each.value
route_table_id = aws_route_table.main.id
}
When working with dynamic lists of identifiers, for_each is safer than count. As implemented in internal/terraform/transform_variable.go, for_each uses the actual subnet ID as the state key rather than a positional index. This prevents the cascade of recreations that occurs when removing items from a counted list.
Common Pitfall: Count Based on Computed Values
# Bad: count depends on a data source that changes during the same plan
data "aws_availability_zones" "available" {}
resource "aws_subnet" "bad" {
count = length(data.aws_availability_zones.available.names)
# ...
}
This pattern creates count drift. Because the count expression depends on a computed data source, the ResourceCountTransformer may see a different value during planning than what exists in state. When the count decreases, the OrphanCountTransformer in internal/terraform/transform_orphan_count.go marks the excess instances for destruction, potentially causing unexpected downtime.
Key Source Files in the Terraform Codebase
Understanding the internal implementation of count helps predict how Terraform will behave when your configuration changes. These files in the HashiCorp Terraform repository control the expansion and lifecycle of counted resources:
| File | Role in Count Handling | GitHub Link |
|---|---|---|
internal/terraform/transform_resource_count.go |
Contains the ResourceCountTransformer that expands a single resource or module into N distinct graph vertices, injecting count.index into each instance's evaluation context. |
https://github.com/hashicorp/terraform/blob/main/internal/terraform/transform_resource_count.go |
internal/terraform/transform_orphan_count.go |
Implements the OrphanCountTransformer that detects instances no longer covered by the current count value and schedules them for destruction. |
https://github.com/hashicorp/terraform/blob/main/internal/terraform/transform_orphan_count.go |
internal/terraform/validate_selfref.go |
Validates that count expressions do not contain self-references that would create circular dependencies during graph construction. |
https://github.com/hashicorp/terraform/blob/main/internal/terraform/validate_selfref.go |
internal/terraform/transform_variable.go |
Handles for_each transformations, providing the contrast to how count operates by using map keys rather than integer indices. |
https://github.com/hashicorp/terraform/blob/main/internal/terraform/transform_variable.go |
Summary
- Terraform count is appropriate for creating a fixed number of homogeneous resources where the count value is stable and known ahead of time, such as validated numeric variables or constants.
- The ResourceCountTransformer in
internal/terraform/transform_resource_count.goexpands count into distinct graph vertices early in the plan phase, while the OrphanCountTransformer handles cleanup when counts decrease. - Avoid count when working with dynamic collections or computed values that change during planning; use for_each instead to prevent unnecessary resource recreation and orphan detection issues.
- Never use count with expressions dependent on data sources or resource attributes that aren't known until apply time, as this causes count drift and potential destruction of existing infrastructure.
Frequently Asked Questions
Should I use count or for_each in Terraform?
Use count when you need a specific number of identical resources and you have a stable, numeric value (like a constant or validated variable) that won't change unexpectedly. Use for_each when iterating over a map or set of strings, especially when the collection membership might change; for_each preserves the relationship between your configuration key and the state key, preventing the cascade of recreations that occurs when removing items from a counted list.
Why does changing count cause resources to be recreated?
When you decrease the count value, the OrphanCountTransformer in internal/terraform/transform_orphan_count.go marks the excess instances (those with indices no longer in range) for destruction. When you increase count, Terraform creates new instances with the next sequential indices. If you remove an item from the middle of a list used with count, every subsequent resource shifts index, causing Terraform to plan destruction and recreation for all downstream instances because the state keys (e.g., resource_name[1], resource_name[2]) no longer map to the same configuration values.
Can I use count with modules in Terraform?
Yes, you can use the count meta-argument with module blocks exactly as you do with resources. When you apply count to a module, the ResourceCountTransformer creates distinct instances of the entire module, each with its own isolated state namespace and count.index value. This is useful for creating multiple identical environments or node pools, but the same cautions apply: avoid using computed values in the module count expression to prevent orphan detection and unexpected destruction.
What happens when I decrease the count value?
When you decrease the count value and run terraform apply, the OrphanCountTransformer identifies which instances now fall outside the new count range (e.g., if you reduce count from 5 to 3, indices 3 and 4 become orphans). Terraform schedules these orphaned instances for destruction during the apply phase. The remaining instances (indices 0-2) are preserved unchanged. This behavior is why you should never use count with lists where order matters for identity; removing an item from the middle of the list effectively decreases the count for all subsequent items, triggering their destruction and recreation with new indices.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s https://instagit.com/install.md