How to Use terraform merge maps to Combine Maps Inside a List

Use the merge() function with the splat operator ... to expand a list of maps into individual arguments, producing a single map where right-most values overwrite duplicate keys.

When working with infrastructure configurations in the hashicorp/terraform repository, you frequently encounter scenarios where multiple map objects—such as tag sets or environment variables—are stored as elements within a list. The terraform merge maps capability allows you to flatten these structures into a unified map for use in resource arguments, enabling deterministic configuration layering and consolidation.

Understanding the terraform merge maps Implementation

The merge function is implemented as a pure, deterministic operation in Terraform's core language. According to the source code in internal/lang/funcs/merge.go, the function accepts a variadic number of map arguments and returns a single map containing the union of all key-value pairs. The HCL evaluator registers this function via configs/hcl2/expr_functions.go, making it available anywhere Terraform evaluates expressions, including variables, locals, and resource attributes.

Key characteristics of the implementation include:

  • Right-most precedence: When duplicate keys exist across input maps, the value from the map appearing last in the argument list prevails.
  • Type uniformity: All maps must share the same value type (e.g., map(string) or map(number)). Mixing types triggers a constraint error during the planning phase.
  • Planning-phase execution: The merge operation occurs during Terraform's evaluation phase, producing no runtime overhead on applied infrastructure.

How to Merge a List of Maps in Terraform

When your maps are stored as elements within a list, you cannot pass the list directly to merge. Instead, you must expand the list into individual arguments using the splat operator (...).

Using the Splat Operator for Dynamic Lists

The idiomatic pattern for merging an arbitrary number of maps from a list uses the expansion syntax:

locals {
  tag_sets = [
    { Owner = "alice", Env = "dev" },
    { Env = "prod", Project = "website" },
    { CostCenter = "1234" }
  ]

  # Expand the list into merge arguments

  merged_tags = merge(local.tag_sets...)
}

In this example, local.tag_sets... expands to three separate arguments. The resulting merged_tags map contains { Owner = "alice", Env = "prod", Project = "website", CostCenter = "1234" }. Notice that the Env key from the second map overwrote the first because of right-most precedence.

Handling Key Conflicts and Precedence

Understanding precedence is critical when using terraform merge maps for configuration overlays. The function applies a simple left-to-right merge strategy:

locals {
  defaults = { instance_type = "t2.micro", monitoring = false }
  overrides = { instance_type = "t3.large", monitoring = true }

  final_config = merge(local.defaults, local.overrides)
  # Result: { instance_type = "t3.large", monitoring = true }

}

This behavior makes merge ideal for implementing layered configuration patterns, such as applying environment-specific overrides to baseline defaults.

Practical Examples for Real-World Use Cases

Merging Tag Maps from Multiple Sources

Infrastructure tagging often requires combining default organizational tags with resource-specific and environment-specific tags:

variable "additional_tags" {
  type    = list(map(string))
  default = [
    { Team = "platform" },
    { CostCenter = "cc-789" }
  ]
}

locals {
  mandatory_tags = {
    ManagedBy = "terraform"
    Environment = "production"
  }

  all_resource_tags = merge(local.mandatory_tags, var.additional_tags...)
}

resource "aws_instance" "example" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"
  tags          = local.all_resource_tags
}

Consolidating Environment Configurations

When managing multiple environment configurations stored as a list of objects, extract and merge specific map attributes:

locals {
  config_fragments = [
    { name = "db",   settings = { version = "12.0", storage = "100gb" } },
    { name = "cache",settings = { version = "6.2",  storage = "20gb" } },
    { name = "web",  settings = { version = "nginx",storage = "10gb" } }
  ]

  # Extract nested maps and merge them

  merged_settings = merge([for f in local.config_fragments : f.settings]...)
}

Note that this pattern uses a for expression to extract the nested maps before applying the splat operator.

Combining Variable Inputs with Local Defaults

For module authors, merging allows flexible input patterns while maintaining sensible defaults:

variable "policy_statements" {
  type    = list(map(any))
  default = []
}

locals {
  default_statement = {
    Effect   = "Allow"
    Action   = ["s3:GetObject"]
    Resource = ["*"]
  }

  effective_statements = length(var.policy_statements) > 0 ? merge(var.policy_statements...) : local.default_statement
}

Performance and Implementation Details

The terraform merge maps operation executes entirely during Terraform's planning phase. As implemented in internal/lang/funcs/merge.go, the function performs a shallow copy of map elements, creating no persistent runtime objects in the final infrastructure state. This means merging hundreds of maps incurs negligible performance cost beyond the initial configuration evaluation.

For authoritative reference, consult the official documentation in docs/language/functions/merge.html.markdown and the practical examples located in examples/functions/merge/ within the hashicorp/terraform repository.

Summary

  • Use the splat operator (...) to expand a list of maps into individual arguments for the merge() function.
  • Right-most precedence applies: when keys conflict, values from maps appearing later in the argument list override earlier ones.
  • Type uniformity is required: all maps must share the same value type (e.g., map(string)), enforced during the planning phase.
  • Zero runtime impact: the merge operation executes during configuration evaluation in internal/lang/funcs/merge.go, producing no infrastructure-side overhead.
  • Common patterns include consolidating tag maps, layering environment configurations, and combining module defaults with variable inputs.

Frequently Asked Questions

How does terraform merge maps handle duplicate keys?

When duplicate keys exist across input maps, terraform merge maps applies right-most precedence. The value from the map that appears last in the argument list—or latest in the list when using the splat operator ...—overwrites any previous values for that key. This deterministic behavior is hardcoded in internal/lang/funcs/merge.go and enables intentional configuration layering.

Can I merge maps with different value types in Terraform?

No, terraform merge maps requires type uniformity. All maps passed to the merge() function must share the same value type, such as map(string) or map(number). Attempting to merge a map(string) with a map(number) triggers a type constraint error during the planning phase. If you need to combine heterogeneous data, first normalize values to a common type using for expressions or tolist() conversions.

Is there a performance impact when merging large lists of maps?

No significant performance impact occurs when using terraform merge maps with large lists. The merge() function executes during Terraform's planning phase as a pure function implemented in internal/lang/funcs/merge.go. It performs shallow copies of map structures without creating persistent runtime objects in the state file. Whether merging three maps or three hundred, the operation completes during configuration evaluation and imposes no overhead on the actual infrastructure resources.

How do I merge maps conditionally based on a variable?

To conditionally merge maps using terraform merge maps, combine the merge() function with conditional expressions or the length() function to check for empty inputs. For example, use length(var.optional_tags) > 0 ? merge(local.base_tags, var.optional_tags...) : local.base_tags to only merge when the variable contains elements. Alternatively, use try() or coalesce() patterns to handle null values, ensuring the merge() function always receives valid map arguments.

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:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client