TypeScript Record Type: Building Type-Safe Dictionaries
Problem
You’re managing status code messages in an object, but when a new code is added to the union, TypeScript doesn’t catch the missing entry. Index signatures like { [key: string]: string } are too loose.
Solution
Record<K, V> enforces both keys and values at the type level.
type StatusCode = 'success' | 'error' | 'pending' | 'timeout';
const statusMessages: Record<StatusCode, string> = {
success: 'Completed',
error: 'An error occurred',
pending: 'Processing',
timeout: 'Request timed out',
};
// Missing any key = compile error
Adding a new value to StatusCode immediately flags statusMessages as incomplete.
Combine with enums for stronger guarantees:
enum Permission {
Read = 'read',
Write = 'write',
Delete = 'delete',
}
const permissionLabels: Record<Permission, string> = {
[Permission.Read]: 'Read',
[Permission.Write]: 'Write',
[Permission.Delete]: 'Delete',
};
When not all keys are required, wrap with Partial:
const overrides: Partial<Record<StatusCode, string>> = {
error: 'Server error',
};
Works well for nested structures too:
type Locale = 'ko' | 'en' | 'ja';
const translations: Record<Locale, Record<string, string>> = {
ko: { greeting: '안녕하세요' },
en: { greeting: 'Hello' },
ja: { greeting: 'こんにちは' },
};
Key Points
Record<K, V>requires a value of typeVfor every key inK- Union types or enums as keys catch missing entries at compile time
- Use
Partial<Record<K, V>>when only some keys are needed - Far more precise than
{ [key: string]: V }index signatures - Common in config mappings, i18n, and state management