> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Using for each

> Explains Terraform's for_each to create stable keyed resources, preventing index shifting versus count, with Azure examples and best practices for safe resource management.

In this lesson we cover Terraform's `for_each` meta-argument — the recommended way to create multiple resources when instance identity and lifecycle stability matter. Unlike `count`, `for_each` ties each resource instance to a meaningful key, which prevents index-shifting and makes changes safer and more predictable for production workloads.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/9l60TCpe5-axPZIp/images/Terraform-On-Azure/Iteration-and-Logic/Using-for-each/using-for-each-gradient-blue-background.jpg?fit=max&auto=format&n=9l60TCpe5-axPZIp&q=85&s=d8e02df5cbb79c84e8b726e19fdc6ba6" alt="The image has a gradient blue background with the text &#x22;Using for_each&#x22; in white in the center." width="1920" height="1080" data-path="images/Terraform-On-Azure/Iteration-and-Logic/Using-for-each/using-for-each-gradient-blue-background.jpg" />
</Frame>

Why use `for_each`? When resources are keyed by meaningful identifiers (names, subnet IDs, DNS names, etc.), Terraform can track each resource independently. This prevents accidental destruction and recreation caused by reordering or changing collections that are tracked only by numeric indexes.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/9l60TCpe5-axPZIp/images/Terraform-On-Azure/Iteration-and-Logic/Using-for-each/using-for-each-blue-gradient-background.jpg?fit=max&auto=format&n=9l60TCpe5-axPZIp&q=85&s=0a7c2444a7414d5cdcadd497f6781f62" alt="The image has a blue gradient background with the text &#x22;Using for_each&#x22; in the center. In the bottom left corner, it says &#x22;Copyright KodeKloud&#x22;." width="1920" height="1080" data-path="images/Terraform-On-Azure/Iteration-and-Logic/Using-for-each/using-for-each-blue-gradient-background.jpg" />
</Frame>

Example: create a resource group for every element in a set

```hcl theme={null}
variable "resource_groups" {
  type    = set(string)
  default = ["rg-dev", "rg-prod"]
}

resource "azurerm_resource_group" "rg" {
  for_each = var.resource_groups
  name     = each.key
  location = "eastus"
}
```

Key notes about this example:

* `variable.resource_groups` is declared as `set(string)`: a collection of unique values without a guaranteed order.
* `for_each = var.resource_groups` creates one resource instance per element in the set.
* When iterating a set, both `each.key` and `each.value` refer to the element value (e.g., `"rg-dev"` and `"rg-prod"`). When iterating a map, `each.key` is the map key and `each.value` is the map value.

Because `for_each` uses meaningful keys rather than numeric indexes, Terraform state does not shift when you add or remove other items. This makes `for_each` far safer than `count` in many production scenarios.

<Callout icon="lightbulb" color="#1CB2FE">
  Prefer `for_each` when each resource has a meaningful identifier (name, subnet ID, DNS name, etc.). Use `count` only for N identical resources where individual identity or lifecycle does not matter.
</Callout>

Why index-based `count` is brittle

With `count`, Terraform identifies resources by their numeric index (0, 1, 2, ...). Reordering or removing elements can change those indexes and cause Terraform to destroy and recreate resources unnecessarily.

count-style example (index-based):

```hcl theme={null}
variable "text" {
  default = "rg"
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg" {
  count    = 3
  name     = "${var.text}-resources-${count.index}"
  location = "westeurope"
}

resource "azurerm_storage_account" "sa" {
  count                     = 3
  resource_group_name       = azurerm_resource_group.rg[0].name
  location                  = azurerm_resource_group.rg[0].location
  account_tier              = "Standard"
  account_replication_type  = "LRS"
}
```

A truncated plan showing index-based change:

```plaintext theme={null}
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
  - destroy

Terraform will perform the following actions:

# azurerm_resource_group.rg[1] will be created
+ resource "azurerm_resource_group" "rg" {
  + id       = (known after apply)
  + location = "westeurope"
  + name     = "rg-resources-1"
}

# azurerm_resource_group.rg[2] will be destroyed
- resource "azurerm_resource_group" "rg" {
  - id       = ".../resourceGroups/rg-resources-2" -> null
  - location = "westeurope" -> null
  - name     = "rg-resources-2" -> null
  - tags     = {} -> null
  -/ # etc.
}

Plan: 1 to add, 0 to change, 1 to destroy.
```

Convert the same example to `for_each` to avoid index shifting

```hcl theme={null}
variable "text" {
  default = "rg"
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg" {
  for_each = toset(["0", "1", "2"])
  name     = "${var.text}-resources-${each.key}"
  location = "West Europe"
}

resource "azurerm_storage_account" "sa" {
  name                      = "stfx0953764"
  resource_group_name       = azurerm_resource_group.rg["0"].name
  location                  = azurerm_resource_group.rg["0"].location
  account_tier              = "Standard"
  account_replication_type  = "LRS"
}
```

Notes on referencing `for_each` results:

* A resource using `for_each` becomes a map keyed by the iteration key. Reference an instance with `azurerm_resource_group.rg["0"].name`.
* Avoid mixing index-based references (e.g., `rg[0]`) with map-style lookups from `for_each`. Use map-style lookups consistently.

Quick comparison: `for_each` vs `count`

| Aspect                           | `for_each`                                                      | `count`                                   |
| -------------------------------- | --------------------------------------------------------------- | ----------------------------------------- |
| Identity                         | Stable keys (strings/maps)                                      | Numeric indexes                           |
| Best for                         | Resources with meaningful identifiers (names, IDs, DNS records) | N indistinguishable, ordinal resources    |
| Reference style                  | `resource.name["key"]`                                          | `resource.name[index]`                    |
| Resilience to reordering/removal | High — keyed instances remain stable                            | Low — index shifts cause recreate/destroy |

Typical local workflow

```bash theme={null}
terraform init
terraform plan
terraform apply --auto-approve
```

Example apply output (abridged):

```plaintext theme={null}
Plan: 4 to add, 0 to change, 0 to destroy.

azurerm_resource_group.rg["0"]: Creating...
azurerm_resource_group.rg["0"]: Creation complete after 23s [id=/subscriptions/.../resourceGroups/rg-resources-0]
azurerm_resource_group.rg["1"]: Creating...
azurerm_resource_group.rg["1"]: Creation complete after 23s [id=/subscriptions/.../resourceGroups/rg-resources-1]
azurerm_resource_group.rg["2"]: Creating...
azurerm_resource_group.rg["2"]: Creation complete after 23s [id=/subscriptions/.../resourceGroups/rg-resources-2]
azurerm_storage_account.sa: Creating...
azurerm_storage_account.sa: Creation complete after 1m1s [id=/subscriptions/.../resourceGroups/rg-resources-0/providers/Microsoft.Storage/storageAccounts/stfx0953764]

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
```

Targeting and destroying a single `for_each` instance

* Using `terraform destroy --target 'azurerm_resource_group.rg["0"]'` or `terraform apply -target=...` is possible but generally discouraged for routine operations. Terraform will warn you when targeting because the plan may not represent all configuration changes.

Example warning when using `-target` (abridged):

```plaintext theme={null}
Warning: Resource targeting is in effect

You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
The -target option is not for routine use...
```

<Callout icon="warning" color="#FF6B6B">
  Avoid relying on `-target` for normal workflows. Targeting can produce incomplete or unsafe plans when resources have dependencies, and may cause unintended changes.
</Callout>

Recommended pattern to remove an instance managed by `for_each`

1. Update the collection used by `for_each` to remove the key you want to delete.
2. Run `terraform plan` and `terraform apply`. Terraform will plan and destroy only the resource whose key was removed and leave other keyed instances intact.

Example: remove the instance with key `"1"`

Before:

```hcl theme={null}
variable "resource_groups" {
  type    = set(string)
  default = ["0", "1", "2"]
}
```

After (removed `"1"`):

```hcl theme={null}
variable "resource_groups" {
  type    = set(string)
  default = ["0", "2"]   # removed "1"
}
```

Then:

```bash theme={null}
terraform plan
terraform apply
```

Resulting plan shows only the removed key scheduled for destruction:

```plaintext theme={null}
# azurerm_resource_group.rg["1"] will be destroyed
- resource "azurerm_resource_group" "rg" {
    id       = "/subscriptions/.../resourceGroups/rg-resources-1"
    name     = "rg-resources-1" -> null
    location = "West Europe" -> null
    tags     = {} -> null
}
Plan: 0 to add, 0 to change, 1 to destroy.
```

Summary and best practices

* Use `for_each` when instances have meaningful identities (names, subnet IDs, DNS records, etc.). It maps instances to stable keys and prevents index-driven recreation.
* Use `count` only when you truly need N indistinguishable, ordinal resources.
* Reference `for_each` instances with `resource.name["key"]`, and avoid mixing indexing styles.
* To remove a single instance, remove its key from the `for_each` collection and `apply` the change. Avoid `-target` unless you fully understand the dependency implications.

Links and references

* [Terraform: Meta-arguments — for\_each and count](https://www.terraform.io/docs/language/meta-arguments/for_each.html)
* [Terraform: Expressions — each object](https://www.terraform.io/docs/language/expressions/each.html)
* [Azure Provider (azurerm) Documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/terraform-on-azure/module/fb5019bb-df21-4583-818e-6dae40fde2ec/lesson/316db583-71fc-4563-8f19-72bbf9be8a2b" />
</CardGroup>
