An index signature describes the type of properties when you don't know their names ahead of time — modeling dictionaries/maps where keys are dynamic.
{
[: ]: ;
}
: = { : , : };
scores. = ;
scores.;
scores.;
An index signature describes the type of properties when you don't know their names ahead of time — modeling dictionaries/maps where keys are dynamic.
{
[: ]: ;
}
: = { : , : };
scores. = ;
scores.;
scores.;
With a plain index signature, the compiler assumes every key exists, so scores.typo is typed as number even if it's actually undefined at runtime. Enabling noUncheckedIndexedAccess fixes this:
// with noUncheckedIndexedAccess: true
scores.math; // number | undefined → forces you to handle the missing case
interface Config {
name: string; // known property
[key: string]: string | number; // plus arbitrary extra keys
}
All named properties must be assignable to the index signature's value type.
type Scores = Record<string, number>; // same as the index signature
type Roles = Record<"admin" | "user", boolean>; // constrained keys
Record is the idiomatic shorthand and supports a restricted key union, which a bare [key: string] can't.
Index signatures model genuinely dynamic-keyed objects (lookup tables, caches, parsed JSON maps).
Know the safety caveat (noUncheckedIndexedAccess) and prefer Record<K, V> for readability — and prefer a Map when keys are truly open-ended and you want real iteration/size semantics.