Difficulty: Intermediate
What are index signatures in TypeScript? How do they compare to the Record utility type?
Index signatures define the type for properties accessed by a dynamic key. They declare that all properties accessed by a certain key type must have a certain value type.
Syntax: { [key: string]: ValueType }
Record<K, V> is a utility type that creates an object type with keys of type K and values of type V. It is implemented using mapped types and is often preferred over index signatures for clarity.
Key differences: - Index signatures allow any string/number key. Record with a union restricts to specific keys. - Index signatures mix with explicit properties. Record creates a standalone type. - Record<string, V> is equivalent to { [key: string]: V }.
// String index signature
interface Dictionary {
[key: string]: string;
}
const dict: Dictionary = {
hello: 'world',
foo: 'bar',
};
dict['anything'] = 'works'; // any string key is valid
// Mixing index signatures with known properties
interface Config {
name: string; // explicit property
version: number; // explicit property
[key: string]: string | number; // all other keys
}
// Note: explicit properties must be assignable to the index signature type
// Number index signature (for array-like objects)
interface StringArray {
[index: number]: string;
length: number;
}
const arr: StringArray = { 0: 'a', 1: 'b', length: 2 };
// Readonly index signature
interface ReadonlyMap {
readonly [key: string]: number;
}
const scores: ReadonlyMap = { alice: 95, bob: 87 };
// scores['alice'] = 100; // Error: readonly
Index signatures handle dynamic keys. When mixing with explicit properties, every explicit property must be assignable to the index signature's value type.
// Record with string keys (like index signature)
type Cache = Record<string, unknown>;
const cache: Cache = { user_1: { name: 'Alice' }, count: 42 };
// Record with union keys (exhaustive)
type Theme = 'light' | 'dark' | 'system';
type ThemeConfig = Record<Theme, { bg: string; text: string }>;
const themes: ThemeConfig = {
light: { bg: '#fff', text: '#000' },
dark: { bg: '#000', text: '#fff' },
system: { bg: '#f0f0f0', text: '#333' },
// Must have ALL three keys - missing one is an error
};
// Record vs index signature
// Index signature: open-ended, any key
interface OpenMap { [key: string]: number; }
// Record with union: closed, only listed keys
type ClosedMap = Record<'a' | 'b' | 'c', number>;
const closed: ClosedMap = { a: 1, b: 2, c: 3 };
// closed.d; // Error: Property 'd' does not exist
// Nested Record
type UserPermissions = Record<string, Record<string, boolean>>;
const perms: UserPermissions = {
admin: { read: true, write: true, delete: true },
user: { read: true, write: false, delete: false },
};
Record with union keys creates exhaustive mappings that require all keys. Record with string keys behaves like an index signature but is often more readable.
// Type-safe dictionary with Map-like interface
interface TypedMap<V> {
[key: string]: V | undefined; // undefined acknowledges missing keys
}
function getOrDefault<V>(map: TypedMap<V>, key: string, fallback: V): V {
return map[key] ?? fallback;
}
const ages: TypedMap<number> = { alice: 30, bob: 25 };
const age = getOrDefault(ages, 'charlie', 0); // 0
// Grouping pattern
function groupBy<T>(items: T[], key: keyof T): Record<string, T[]> {
return items.reduce((groups, item) => {
const value = String(item[key]);
(groups[value] ??= []).push(item);
return groups;
}, {} as Record<string, T[]>);
}
interface Product { category: string; name: string; price: number; }
const products: Product[] = [
{ category: 'books', name: 'TS Handbook', price: 29 },
{ category: 'books', name: 'Clean Code', price: 35 },
{ category: 'tools', name: 'VS Code', price: 0 },
];
const grouped = groupBy(products, 'category');
// { books: [Product, Product], tools: [Product] }
Adding '| undefined' to index signatures acknowledges that not every key may be present. This is safer than assuming all lookups succeed.
Index Signatures, Record Type, Dictionary Patterns, Dynamic Keys, Symbol Keys