TerraformPilot

DevOps

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

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.

LLuca Berton1 min read

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.

#Terraform#Troubleshooting#DevOps#Error Fix#Infrastructure as Code

Share this article