Hint
Record<K, V> creates an object type with keys K and values V — great for lookup maps and indexed objects
Record<K, V> constructs an object type with keys of type K and values of type V.
// Simple lookup map
type CountryCode = 'US' | 'UK' | 'DE';
const currencies: Record<CountryCode, string> = {
US: 'USD',
UK: 'GBP',
DE: 'EUR',
}; // TypeScript ensures all keys are present
// Dynamic key type
type IdMap<T> = Record<string, T>;
const userMap: IdMap<User> = {};
userMap['abc123'] = { id: 1, name: 'Alice' };
Record vs index signature:
// Index signature — key type can be broad
interface Dict {
[key: string]: string;
}
// Record — key type can be a union literal (exhaustive)
type StatusMap = Record<'active' | 'inactive' | 'pending', number>;
// TypeScript enforces all three keys must be present
const counts: StatusMap = {
active: 12,
inactive: 3,
pending: 1,
// missing 'pending' would be an error
};
Common patterns:
// Route config map
type Route = '/home' | '/about' | '/users';
const routeTitles: Record<Route, string> = {
'/home': 'Home',
'/about': 'About Us',
'/users': 'Users',
};
// Grouping array items
function groupBy<T, K extends string>(
items: T[],
getKey: (item: T) => K
): Partial<Record<K, T[]>> {
return items.reduce((acc, item) => {
const key = getKey(item);
(acc[key] ??= []).push(item);
return acc;
}, {} as Partial<Record<K, T[]>>);
}
Record<'a' | 'b', T> instead of a plain object type when you want TypeScript to enforce that all union members are handled — like an exhaustive switch but for object keys.