hashicorp/terraform · error

length of keys and values should be equal

Error message

length of keys and values should be equal

What it means

Thrown by `matchkeys()` (MatchkeysFunc) in its Impl when `values` (1st arg) and `keys` (2nd arg) have different lengths. matchkeys pairs values[i] with keys[i] positionally, so the two lists must have equal length; a mismatch is a logic error and the function returns an empty list plus this error.

Solutions

  1. Ensure values and keys are derived from the same source so their lengths stay in sync.
  2. Validate length equality before calling: guard with `length(var.values) == length(var.keys)`.
  3. Restructure as a single list of objects {value=..., key=...} so pairing cannot drift.

Example fix

// before
locals { r = matchkeys(var.names, var.ids, ["prod"]) }  # names and ids differ in length

// after
locals {
  pairs = [for i, n in var.names : { name = n, id = var.ids[i] }]  # built from one source
  r     = [for p in local.pairs : p.name if contains(["prod"], p.id)]
}
Defensive patterns

Strategy: validation

Validate before calling

# HCL: assert equal length before matchkeys
locals {
  ok = length(var.values) == length(var.keys)
  r  = local.ok ? matchkeys(var.values, var.keys, var.searchset) : []
}

Type guard

# HCL: derive both from one source list of objects
locals {
  pairs = [for p in var.records : { value = p.value, key = p.key }]
}

Prevention

When it happens

Trigger: Calling `matchkeys(values, keys, searchset)` where len(values) != len(keys). One list was filtered or extended independently of the other.

Common situations: Two parallel lists maintained in separate locals/blocks that drifted out of sync. A comprehension that built values from a superset of the source used for keys.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/02785d499d6ada09. Report an issue: GitHub.

Appendix: source

Thrown at internal/lang/funcs/collection.go:365

		},
	},
	Type: func(args []cty.Value) (cty.Type, error) {
		ty, _ := convert.UnifyUnsafe([]cty.Type{args[1].Type(), args[2].Type()})
		if ty == cty.NilType {
			return cty.NilType, errors.New("keys and searchset must be of the same type")
		}

		// the return type is based on args[0] (values)
		return args[0].Type(), nil
	},
	RefineResult: refineNotNull,
	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
		if !args[0].IsKnown() {
			return cty.UnknownVal(cty.List(retType.ElementType())), nil
		}

		if args[0].LengthInt() != args[1].LengthInt() {
			return cty.ListValEmpty(retType.ElementType()), errors.New("length of keys and values should be equal")
		}

		output := make([]cty.Value, 0)
		values := args[0]

		// Keys and searchset must be the same type.
		// We can skip error checking here because we've already verified that
		// they can be unified in the Type function
		ty, _ := convert.UnifyUnsafe([]cty.Type{args[1].Type(), args[2].Type()})
		keys, _ := convert.Convert(args[1], ty)
		searchset, _ := convert.Convert(args[2], ty)

		// if searchset is empty, return an empty list.
		if searchset.LengthInt() == 0 {
			return cty.ListValEmpty(retType.ElementType()), nil
		}

		if !values.IsWhollyKnown() || !keys.IsWhollyKnown() {

View on GitHub (pinned to d32a084675)