Index Signatures & Mapped Types
Quick Overview
An index signature types keys that are not known in advance; a mapped type builds new keys from ones that are. `[K in keyof T]` is what makes `Partial` and `Readonly` ordinary library code rather than compiler magic.
Deep Dive
1. Dynamic Key Dictionaries
In JavaScript, objects often act as lookup dictionaries with unknown dynamic property keys (for example, caching objects or user dictionaries).
2. Index Signatures
An Index Signature defines the data type of dynamic keys and values for objects whose exact property names are not known ahead of time:
// Index signature: key must be string, value must be number
interface UserScores {
[username: string]: number;
}
const leaderboard: UserScores = {
alex: 100,
sarah: 250,
maria: 180,
};
leaderboard.david = 300; // Valid dynamic key addition!
3. Mapped Types Syntax
A Mapped Type builds a new object shape by iterating over keys of an existing type using in keyof syntax:
type Permissions = "canRead" | "canWrite" | "canDelete";
// Mapped Type: Maps every permission string to a boolean!
type UserPermissionFlags = {
[K in Permissions]: boolean;
};
/*
Equivalent to:
type UserPermissionFlags = {
canRead: boolean;
canWrite: boolean;
canDelete: boolean;
};
*/
const myFlags: UserPermissionFlags = {
canRead: true,
canWrite: false,
canDelete: false,
};
4. Modifying Flags in Mapped Types
You can add or remove readonly or optional ? modifiers inside mapped types using + or -:
// Make all properties optional
type Optional<T> = {
[K in keyof T]?: T[K];
};
5. Common Mistakes
- Unchecked Index Access: Reading an index signature property (
leaderboard["unknownUser"]) returnsnumberby default, even though it evaluates toundefinedat runtime! EnablenoUncheckedIndexedAccess: trueintsconfig.jsonto forcenumber | undefinedsafety!
6. Next Steps
Use mapped types for reusable type transformations. Next, let's enter Phase 3 and master Generics!
Next Up in TypeScript & Type System Mechanics
TypeScript Primitives & Explicit Type Annotations
TypeScript erases entirely at build time, so a `string` annotation produces no runtime check whatsoever. A value arriving from an API is whatever the server actually sent, regardless of what the annotation claims.
Type Inference & Literal Types (const vs. let)
`let x = "GET"` infers `string` because the binding can change; `const x = "GET"` infers the literal `"GET"`. That widening rule is why object properties need `as const` to stay narrow.
Arrays, Tuples & Readonly Modifiers
`string[]` is any number of strings; `[string, number]` is exactly two, in that order. `readonly` removes the mutating methods at compile time, so `push` becomes a type error rather than a runtime surprise.
