Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesiTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Terraform variables accept values from a module’s caller, locals give reusable names to expressions inside the module, and outputs expose selected results. For repetition, use count when numbered, nearly identical instances fit the job, and for_each when each instance has a meaningful key. Use dynamic only to generate nested provider-defined blocks—not repeated resources.
How Terraform values flow through a module
A reusable module has an input interface, internal expressions, and outputs. The caller supplies input variables; expressions and locals can transform or reuse those values; resources and data sources consume them; and outputs expose selected results to the caller or other Terraform interfaces. HashiCorp describes module communication through inputs and outputs, with locals for reusable expressions in between.
These constructs are related, but they are not all “variables.” An input variable is declared in a variable block and supplied from outside the module. local.name refers to a named expression. each.key, each.value, count.index, and a dynamic-block iterator are temporary contextual values available only in the relevant block.
How do Terraform input variables work?
Input variables let a module consumer customize behavior without editing the module’s source. A variable can be declared in a root module or child module. A root module receives values from its execution environment; a parent module passes values to a child using arguments in a module block.
#1 Best Overall
For example, a module might accept a deployment name:
variable "environment" {
type = string
description = "Name used to identify this deployment."
}
resource "example_resource" "app" {
name = var.environment
}
The resource is illustrative: actual resource types and arguments depend on the provider schema. A caller can pass the value to a child module like this:
module "app" {
source = "./modules/app"
environment = "staging"
}
For root-module inputs, Terraform supports CLI options, environment variables, variable definition files, and HCP Terraform. Use the input mechanism that fits how the configuration is run; a child module receives its values from its parent’s module arguments.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Choose variable arguments deliberately
typeconstrains the expected value’s shape, such as a string, number, list, or object.defaultmakes the value optional for callers. Without a default, the caller must supply it.descriptionexplains the purpose and expected value.validationexpresses rules that go beyond the type constraint.sensitivesuppresses the value in relevant CLI output. It does not, by itself, prevent the value from being stored in state or plan files.nullablecontrols whether the value may be assignednull.ephemeralandconstare also documented variable features. Check the variable reference for semantics supported by the Terraform version your project pins.
HashiCorp’s style guide recommends ordering variable arguments as type, description, optional default, optional sensitive, then validation. Descriptive names and clear descriptions make module interfaces easier to use.
When should you use locals?
Use a local when an expression deserves a name or needs to be reused within the same module. Locals are not inputs for the caller to set. They can refer to variables, resources, data sources, and other locals.
locals {
resource_name = "${var.environment}-app"
}
resource "example_resource" "app" {
name = local.resource_name
}
This keeps a derived value in one place rather than duplicating its expression. Use var.environment for a caller-provided value and local.resource_name for a value computed inside the module.
Should you use count, for_each, or a dynamic block?
The key distinction is what gets repeated. count and for_each create instances of supported Terraform blocks. A dynamic block generates nested configuration blocks inside a containing block. They are not interchangeable loop syntaxes.
Free tools Windows power users keep installed
One-click scans. No signup required.
| Construct | What it repeats | Identity and access | Best fit | Important constraint |
|---|---|---|---|---|
count |
Instances of supported blocks, including resources and modules | Numeric index; address an instance by index | Nearly identical instances where a number or position is a natural identity | Cannot be used in the same block as for_each |
for_each |
Instances of supported blocks, including resources, modules, and data sources | Map key or set member; use each.key and each.value |
Instances with meaningful identities or distinct values | Keys or set members must be known before apply; sensitive values are not allowed |
dynamic |
Nested configuration blocks | Iterator object with key and value |
A provider-defined nested block needs to be generated from a collection | Cannot generate Terraform meta-argument blocks such as lifecycle or provisioner |
How do count and for_each differ?
Use count for numbered, similar instances
count takes a number and creates that many instances. Within the block, count.index is the numeric index for the current instance. A counted resource or module is addressed as a list of instances, such as example_resource.app[0]. Choose it when the instances are nearly identical and indexing them makes sense.
variable "replica_count" {
type = number
description = "Number of app instances to create."
default = 2
}
resource "example_resource" "app" {
count = var.replica_count
name = "app-${count.index}"
}
This example creates indexed instances. If the collection can change in ways that make position a poor identity, a map or set with meaningful keys may better express the intended instances.
Use for_each when identity matters
for_each accepts a map or a set of strings for resources and modules. Each map key or set member identifies an instance. Inside the block, each.key gives that identity and each.value gives its associated value.
variable "app_names" {
type = set(string)
description = "Names that identify app instances."
default = ["api", "worker"]
}
resource "example_resource" "app" {
for_each = var.app_names
name = each.key
}
These instances are addressed by key, for example example_resource.app["api"]. With a map, the key can identify an instance while the value supplies its configuration:
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchvariable "apps" {
type = map(object({
size = string
}))
description = "App settings keyed by app name."
}
resource "example_resource" "app" {
for_each = var.apps
name = each.key
size = each.value.size
}
Choose stable, understandable keys that represent the intended instances. Terraform requires the map keys or set members to be known before apply. They cannot depend on impure functions such as uuid, bcrypt, or timestamp, and sensitive values cannot be used as for_each arguments because instance identities are shown in Terraform’s UI.
Rank #4
Do not put count and for_each in one block. Select the one whose identity model best matches the data and how you need to address the instances.
What is the difference between for_each and a dynamic block?
A resource-level for_each repeats resource instances. A dynamic block repeats nested blocks within one containing resource, data source, provider, or provisioner block. The provider schema determines which nested block types are valid.
A dynamic block names the nested block type in its label, iterates a collection, and uses content to define each generated block. The iterator defaults to the dynamic block label; an iterator argument can give it a different name. The iterator exposes key and value.
dynamic "setting" {
for_each = var.settings
content {
name = setting.key
value = setting.value
}
}
Here, setting is the default iterator name for the setting block. The example is only valid where the containing block’s provider schema defines a compatible nested setting block and its arguments.
Dynamic blocks cannot generate Terraform meta-argument blocks such as lifecycle or provisioner, which Terraform processes earlier. Prefer literal nested blocks when they are clear and practical; overusing dynamic blocks can make configuration harder to read.
How do outputs expose values from a module?
Outputs expose selected values from a module to its caller, Terraform CLI output, or HCP Terraform. In a child module, the parent refers to a declared output through the child’s module result.
output "app_name" {
description = "Name assigned to the app resource."
value = example_resource.app.name
}
A parent module can consume that output with module.app.app_name. HashiCorp’s style guide recommends giving outputs descriptions and specifying their type for readability and module documentation.
Repetition changes the result’s shape. A module using for_each produces a map keyed by the supplied identities; a module using count produces a list. The same instance-addressing distinction applies when you reference repeated resources. Data-source blocks can also use count or for_each to create separately addressable instances.
Validate against your Terraform and provider versions
Terraform language features and provider resource schemas are separate considerations. Pin and check the Terraform and provider versions used by your project, then validate that examples match those versions and the nested blocks and arguments the provider actually supports. The HashiCorp references below document the language constructs; they do not establish that a provider-specific example works with every provider version.
Quick Recap
Official references
- Variable block reference
- Manage values in modules
- count meta-argument
- for_each meta-argument
- Dynamic blocks
- Configuration language style guide
- Locals block reference
- References to values
- Query data from external sources
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

