Skip to main content
Fix Terraform Error: Index Out of Range (Invalid Index)

Fix Terraform Error: Index Out of Range (Invalid Index)

Key Takeaway

Fix terraform index out of range and invalid index errors. Use try(), lookup(), one(), and length() to safely access list elements and map keys without crashes.

Table of Contents

Quick Answer

# Use try() for safe access
value = try(var.subnets[0], null)
value = try(var.tags["Environment"], "dev")

The Error

Error: Invalid index

  on main.tf line 15:
  15: subnet_id = aws_subnet.private[2].id

The given key does not identify an element in this collection value:
the collection has no element at index 2.

Or:

Error: Invalid index

  on outputs.tf line 3:
   3: value = local.config["missing_key"]

The given key does not identify an element in this collection value.

What Causes This

  1. List shorter than expected — accessing index 2 in a list of 2 items (0,1)
  2. Map key doesn’t exist — referencing a key that’s not in the map
  3. count/for_each produced fewer resources — conditional created 0 items
  4. Data source returned empty results — no matching resources found
  5. Variable default is emptydefault = [] or default = {}

Solution 1: try() — The Best Fix

try() returns the first expression that doesn’t error:

# Safe list access
locals {
  first_subnet = try(aws_subnet.private[0].id, null)
  second_az    = try(data.aws_availability_zones.available.names[1], "us-east-1b")
}

# Safe map access
locals {
  env = try(var.config[terraform.workspace], var.config["dev"])
}

# Safe nested access
locals {
  db_port = try(var.services.database.port, 5432)
}

Solution 2: lookup() for Maps

# lookup(map, key, default)
locals {
  instance_type = lookup(var.instance_types, terraform.workspace, "t3.micro")

  # Equivalent to:
  # var.instance_types["prod"]  → "t3.large"  (if exists)
  # var.instance_types["test"]  → "t3.micro"  (default)
}

variable "instance_types" {
  default = {
    dev  = "t3.micro"
    prod = "t3.large"
  }
}

Solution 3: length() Check

# Check before accessing
output "first_subnet" {
  value = length(aws_subnet.private) > 0 ? aws_subnet.private[0].id : null
}

# Conditional resource based on list
resource "aws_route_table_association" "private" {
  count     = length(aws_subnet.private)
  subnet_id = aws_subnet.private[count.index].id
  # ...
}

Solution 4: one() for Single Elements

# one() returns the single element or null if empty
data "aws_vpc" "selected" {
  filter {
    name   = "tag:Environment"
    values = ["prod"]
  }
}

locals {
  vpc_id = one(data.aws_vpc.selected[*].id)
}

Solution 5: coalesce() and coalescelist()

# First non-null value
locals {
  region = coalesce(var.override_region, var.default_region, "us-east-1")
}

# First non-empty list
locals {
  subnets = coalescelist(var.custom_subnets, data.aws_subnets.default.ids)
}

Common Patterns

Safe Data Source Access

data "aws_instances" "web" {
  filter {
    name   = "tag:Role"
    values = ["web"]
  }
}

# Don't do this — crashes if no instances
output "first_ip" {
  value = data.aws_instances.web.private_ips[0]   # ❌
}

# Do this instead
output "first_ip" {
  value = try(data.aws_instances.web.private_ips[0], "none")   # ✅
}

Conditional count Access

resource "aws_eip" "nat" {
  count = var.enable_nat ? 1 : 0
}

# Don't do this
output "nat_ip" {
  value = aws_eip.nat[0].public_ip   # ❌ Crashes if enable_nat = false
}

# Do this
output "nat_ip" {
  value = try(aws_eip.nat[0].public_ip, null)   # ✅
}

# Or this
output "nat_ip" {
  value = var.enable_nat ? aws_eip.nat[0].public_ip : null   # ✅
}

for_each Map Access

resource "aws_instance" "app" {
  for_each = var.instances
  # ...
}

# Access specific instance safely
output "web_ip" {
  value = try(aws_instance.app["web"].private_ip, null)
}

Function Reference

FunctionUse Case
try(expr, default)Safe access to any expression
lookup(map, key, default)Safe map key lookup
one(list)Single element or null
coalesce(a, b, c)First non-null value
coalescelist(a, b)First non-empty list
length(list)Check list size before access
contains(list, value)Check if value exists
can(expr)Returns true if expression succeeds

Hands-On Courses

Conclusion

Use try() as your default safe-access pattern — it works for lists, maps, and nested expressions. Use lookup() for simple map defaults, length() to guard list access, and one() for single-element data sources. Never access list indexes or map keys directly unless you’re certain they exist.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.