+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 74 of 355

📊 Record Type: Creating Object Types with Dynamic Keys

Master TypeScript's Record utility type to create flexible, type-safe object structures with dynamic keys and consistent value types 🚀

🚀Intermediate
22 min read

Prerequisites

  • Understanding TypeScript object types and interfaces 📝
  • Familiarity with union types and string literals ⚡
  • Basic knowledge of generic types 💻

What you'll learn

  • Master Record<K, V> for creating object types 🎯
  • Use Record with union types for precise key sets 🏗️
  • Build flexible data structures and configurations 🐛
  • Apply Record in real-world mapping scenarios ✨

🎯 Introduction

Welcome to this comprehensive guide on TypeScript’s Record utility type! 🎉 Record is like a blueprint builder for objects - it lets you define object types where you know the value type but want flexibility with the keys.

You’ll discover how Record can transform how you handle configuration objects, data mappings, and lookup tables. Whether you’re building state management systems 🗃️, API response handlers 🌐, or dynamic form configurations 📋, Record provides the perfect balance of type safety and flexibility.

By the end of this tutorial, you’ll be crafting sophisticated object types that adapt to your needs! Let’s dive in! 🏊‍♂️

📚 Understanding Record Type

🤔 What is Record?

Record is like a cookie cutter for objects 🍪. Think of it as a template that says “I want an object where every key follows this pattern, and every value has this specific type.”

In TypeScript terms:

  • Record<K, V> 📋: Creates an object type where keys are of type K and values are of type V
  • K can be string literals, unions, or broader types like string
  • V is the consistent type for all values

This means you can:

  • ✨ Create objects with predictable value types
  • 🚀 Ensure all properties follow the same structure
  • 🛡️ Get type safety for dynamic key access

💡 Why Use Record?

Here’s why developers love Record:

  1. Consistent Value Types 🔄: Every property has the same type
  2. Flexible Keys 🗝️: Keys can be dynamic or from union types
  3. Type-Safe Access 🔒: Get proper autocomplete and error checking
  4. Clean Syntax 📝: Much cleaner than repetitive interface definitions

Real-world example: Imagine building a theme system 🎨. You need color values for different theme variations, but each theme should have the same structure. Record makes this elegant!

🔧 Basic Record Syntax

📝 Simple Record Types

Let’s start with the fundamentals:

// 🎨 Basic Record with string keys and string values
type ColorPalette = Record<string, string>;

const theme: ColorPalette = {
  primary: '#007bff',
  secondary: '#6c757d',
  success: '#28a745',
  danger: '#dc3545',
  warning: '#ffc107',
  info: '#17a2b8'
};

// ✅ All values must be strings
// theme.primary = 123; // ❌ Error: Type 'number' is not assignable to type 'string'

🎯 Record with Union Keys

The real power comes with specific key unions:

// 🌈 Specific color keys only
type ThemeColors = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info';
type Theme = Record<ThemeColors, string>;

const lightTheme: Theme = {
  primary: '#007bff',
  secondary: '#6c757d',
  success: '#28a745',
  danger: '#dc3545',
  warning: '#ffc107',
  info: '#17a2b8'
  // ✅ Must include ALL keys from the union
};

// ❌ Missing keys will cause errors
// const incompleteTheme: Theme = {
//   primary: '#007bff'
//   // Error: Property 'secondary' is missing
// };

🚀 Practical Record Examples

🗃️ Configuration Objects

// ⚙️ API endpoint configuration
type Environment = 'development' | 'staging' | 'production';
type ApiConfig = Record<Environment, {
  baseUrl: string;
  apiKey: string;
  timeout: number;
}>;

const apiConfig: ApiConfig = {
  development: {
    baseUrl: 'http://localhost:3000',
    apiKey: 'dev-key-123',
    timeout: 5000
  },
  staging: {
    baseUrl: 'https://staging-api.example.com',
    apiKey: 'staging-key-456',
    timeout: 10000
  },
  production: {
    baseUrl: 'https://api.example.com',
    apiKey: 'prod-key-789',
    timeout: 15000
  }
};

// 🎯 Usage with type safety
const getApiConfig = (env: Environment) => apiConfig[env];
const prodConfig = getApiConfig('production'); // ✅ Fully typed!

📊 Status Mappings

// 🚦 Order status system
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
type StatusConfig = Record<OrderStatus, {
  label: string;
  color: string;
  canCancel: boolean;
  nextStates: OrderStatus[];
}>;

const orderStatusConfig: StatusConfig = {
  pending: {
    label: 'Order Pending',
    color: '#ffc107',
    canCancel: true,
    nextStates: ['processing', 'cancelled']
  },
  processing: {
    label: 'Processing Order',
    color: '#17a2b8',
    canCancel: true,
    nextStates: ['shipped', 'cancelled']
  },
  shipped: {
    label: 'Order Shipped',
    color: '#007bff',
    canCancel: false,
    nextStates: ['delivered']
  },
  delivered: {
    label: 'Order Delivered',
    color: '#28a745',
    canCancel: false,
    nextStates: []
  },
  cancelled: {
    label: 'Order Cancelled',
    color: '#dc3545',
    canCancel: false,
    nextStates: []
  }
};

// ✨ Type-safe status operations
const getStatusInfo = (status: OrderStatus) => orderStatusConfig[status];
const canCancelOrder = (status: OrderStatus) => orderStatusConfig[status].canCancel;

🎮 Game Data Structures

// 🏆 Character classes in a game
type CharacterClass = 'warrior' | 'mage' | 'archer' | 'rogue';
type ClassStats = Record<CharacterClass, {
  baseHealth: number;
  baseMana: number;
  primaryStat: string;
  abilities: string[];
  description: string;
}>;

const characterClasses: ClassStats = {
  warrior: {
    baseHealth: 120,
    baseMana: 20,
    primaryStat: 'strength',
    abilities: ['Shield Bash', 'Charge', 'Berserker Rage'],
    description: 'Strong melee fighter with high defense 🛡️'
  },
  mage: {
    baseHealth: 60,
    baseMana: 100,
    primaryStat: 'intelligence',
    abilities: ['Fireball', 'Ice Storm', 'Teleport'],
    description: 'Powerful spellcaster with devastating magic ⚡'
  },
  archer: {
    baseHealth: 80,
    baseMana: 40,
    primaryStat: 'dexterity',
    abilities: ['Multi Shot', 'Eagle Eye', 'Rain of Arrows'],
    description: 'Precise ranged combatant with mobility 🏹'
  },
  rogue: {
    baseHealth: 90,
    baseMana: 30,
    primaryStat: 'dexterity',
    abilities: ['Backstab', 'Stealth', 'Poison Blade'],
    description: 'Sneaky assassin with critical strikes 🗡️'
  }
};

// 🎯 Usage in character creation
const createCharacter = (name: string, characterClass: CharacterClass) => {
  const stats = characterClasses[characterClass];
  return {
    name,
    class: characterClass,
    health: stats.baseHealth,
    mana: stats.baseMana,
    abilities: [...stats.abilities] // 📋 Copy abilities array
  };
};

💡 Advanced Record Patterns

🔗 Nested Records

// 🌍 Multi-language translations
type Language = 'en' | 'es' | 'fr' | 'de';
type TranslationKey = 'welcome' | 'goodbye' | 'thankyou' | 'loading';
type Translations = Record<Language, Record<TranslationKey, string>>;

const translations: Translations = {
  en: {
    welcome: 'Welcome!',
    goodbye: 'Goodbye!',
    thankyou: 'Thank you!',
    loading: 'Loading...'
  },
  es: {
    welcome: '¡Bienvenido!',
    goodbye: '¡Adiós!',
    thankyou: '¡Gracias!',
    loading: 'Cargando...'
  },
  fr: {
    welcome: 'Bienvenue!',
    goodbye: 'Au revoir!',
    thankyou: 'Merci!',
    loading: 'Chargement...'
  },
  de: {
    welcome: 'Willkommen!',
    goodbye: 'Auf Wiedersehen!',
    thankyou: 'Danke!',
    loading: 'Wird geladen...'
  }
};

// 🌐 Type-safe translation function
const translate = (lang: Language, key: TranslationKey): string => {
  return translations[lang][key];
};

// ✨ Usage
const welcomeMessage = translate('es', 'welcome'); // "¡Bienvenido!"

🔄 Dynamic Key Generation

// 📅 Calendar event types
type Day = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31;
type Month = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;

// 🗓️ Better approach: use template literals
type DateString = `${Month}/${Day}`;
type EventCalendar = Record<DateString, {
  title: string;
  description: string;
  attendees: string[];
}>;

// 📋 Partial calendar (not all dates required)
const calendar: Partial<EventCalendar> = {
  '1/15': {
    title: 'Team Meeting',
    description: 'Weekly team sync',
    attendees: ['[email protected]', '[email protected]']
  },
  '2/14': {
    title: 'Valentine\'s Day Event',
    description: 'Company celebration',
    attendees: ['[email protected]']
  }
};

🏗️ Record with Complex Value Types

// 🛍️ E-commerce product catalog
type ProductCategory = 'electronics' | 'clothing' | 'books' | 'home' | 'sports';

interface ProductInfo {
  name: string;
  description: string;
  basePrice: number;
  discountPercent: number;
  inStock: boolean;
  tags: string[];
  rating: {
    average: number;
    count: number;
  };
  variants?: {
    color?: string[];
    size?: string[];
  };
}

type CategoryConfig = Record<ProductCategory, {
  displayName: string;
  icon: string;
  defaultSort: 'price' | 'rating' | 'popularity';
  filters: string[];
  sampleProducts: ProductInfo[];
}>;

const categoryConfig: CategoryConfig = {
  electronics: {
    displayName: 'Electronics',
    icon: '📱',
    defaultSort: 'rating',
    filters: ['brand', 'price', 'features'],
    sampleProducts: [
      {
        name: 'Smartphone Pro',
        description: 'Latest flagship phone',
        basePrice: 999.99,
        discountPercent: 10,
        inStock: true,
        tags: ['flagship', 'camera', '5G'],
        rating: { average: 4.8, count: 1247 },
        variants: { color: ['black', 'white', 'blue'] }
      }
    ]
  },
  clothing: {
    displayName: 'Clothing',
    icon: '👕',
    defaultSort: 'popularity',
    filters: ['size', 'color', 'brand', 'material'],
    sampleProducts: [
      {
        name: 'Cotton T-Shirt',
        description: 'Comfortable everyday wear',
        basePrice: 24.99,
        discountPercent: 20,
        inStock: true,
        tags: ['cotton', 'casual', 'unisex'],
        rating: { average: 4.5, count: 892 },
        variants: { 
          color: ['red', 'blue', 'green', 'black'], 
          size: ['S', 'M', 'L', 'XL'] 
        }
      }
    ]
  },
  books: {
    displayName: 'Books',
    icon: '📚',
    defaultSort: 'rating',
    filters: ['genre', 'author', 'publication-year'],
    sampleProducts: [
      {
        name: 'TypeScript Handbook',
        description: 'Complete guide to TypeScript',
        basePrice: 39.99,
        discountPercent: 15,
        inStock: true,
        tags: ['programming', 'typescript', 'web-dev'],
        rating: { average: 4.9, count: 456 }
      }
    ]
  },
  home: {
    displayName: 'Home & Garden',
    icon: '🏠',
    defaultSort: 'price',
    filters: ['room', 'material', 'color'],
    sampleProducts: []
  },
  sports: {
    displayName: 'Sports & Outdoors',
    icon: '⚽',
    defaultSort: 'popularity',
    filters: ['sport', 'brand', 'skill-level'],
    sampleProducts: []
  }
};

⚠️ Common Pitfalls and Solutions

❌ Wrong: Using Record for Everything

// ❌ BAD: Record isn't always the best choice
type User = Record<string, any>; // 😱 No type safety!

const user: User = {
  name: 'John',
  age: 30,
  email: '[email protected]',
  isActive: true,
  favoriteNumbers: [1, 2, 3] // ❌ Could be anything!
};

✅ Right: Use Record When Appropriate

// ✅ GOOD: Use Record for consistent value types
type UserPreferences = Record<string, boolean>;
const preferences: UserPreferences = {
  darkMode: true,
  notifications: false,
  newsletter: true
  // ✅ All values must be boolean
};

// ✅ BETTER: Use interfaces for known structure
interface User {
  name: string;
  age: number;
  email: string;
  isActive: boolean;
  preferences: UserPreferences; // 🎯 Record for dynamic preferences
}

❌ Wrong: Record with Optional Keys

// ❌ PROBLEMATIC: Record requires ALL keys
type RequiredColors = 'primary' | 'secondary' | 'accent';
type Theme = Record<RequiredColors, string>;

// ❌ This will error if any key is missing
const partialTheme: Theme = {
  primary: '#007bff'
  // Error: Property 'secondary' is missing
};

✅ Right: Use Partial with Record

// ✅ GOOD: Use Partial for optional keys
type OptionalTheme = Partial<Record<RequiredColors, string>>;

const partialTheme: OptionalTheme = {
  primary: '#007bff'
  // ✅ Other keys are optional
};

// 🔄 Or create separate types for different needs
type RequiredTheme = Record<'primary' | 'secondary', string>;
type OptionalTheme = Partial<Record<'accent' | 'background', string>>;

🛠️ Best Practices

1. 🎯 Use Record for Homogeneous Data

// ✅ GOOD: All values have the same type
type ErrorMessages = Record<string, string>;
type FormValidation = Record<string, boolean>;
type ApiEndpoints = Record<string, { url: string; method: string }>;

// ❌ BAD: Mixed value types (use interface instead)
type MixedData = Record<string, string | number | boolean>; // 😱 Too generic

2. 🔑 Combine with Union Types for Precision

// ✅ EXCELLENT: Precise keys with consistent values
type HttpStatus = 200 | 404 | 500;
type StatusMessages = Record<HttpStatus, string>;

const messages: StatusMessages = {
  200: 'Success',
  404: 'Not Found',
  500: 'Internal Server Error'
  // ✅ Must include exactly these keys
};

3. 🏗️ Build Reusable Patterns

// 🧱 Reusable patterns with Record
type WithMetadata<T> = T & {
  createdAt: Date;
  updatedAt: Date;
};

type ConfigValue<T> = {
  value: T;
  default: T;
  description: string;
};

// 🎯 Combine for powerful types
type AppSettings = Record<string, ConfigValue<string | number | boolean>>;
type MetadataMap<T> = Record<string, WithMetadata<T>>;

🧪 Hands-On Exercise

Let’s build a state management system! 🗃️

📋 Step 1: Define the Base Types

// 🏗️ State slice structure
interface StateSlice<T> {
  data: T;
  loading: boolean;
  error: string | null;
  lastUpdated: Date | null;
}

// 🎯 Application state slices
interface UserData {
  id: string;
  name: string;
  email: string;
}

interface PostData {
  id: string;
  title: string;
  content: string;
  authorId: string;
}

interface SettingsData {
  theme: 'light' | 'dark';
  language: string;
  notifications: boolean;
}

🎯 Step 2: Create State with Record

// 📊 State slices definition
type StateSlices = 'user' | 'posts' | 'settings';

// 🗃️ Application state using Record
type AppState = Record<StateSlices, StateSlice<any>>;

// 🎯 Better: Type-safe state with specific data types
interface TypedAppState {
  user: StateSlice<UserData>;
  posts: StateSlice<PostData[]>;
  settings: StateSlice<SettingsData>;
}

// ✨ Initial state factory
const createInitialSlice = <T>(initialData: T): StateSlice<T> => ({
  data: initialData,
  loading: false,
  error: null,
  lastUpdated: null
});

const initialState: TypedAppState = {
  user: createInitialSlice({
    id: '',
    name: '',
    email: ''
  }),
  posts: createInitialSlice([]),
  settings: createInitialSlice({
    theme: 'light',
    language: 'en',
    notifications: true
  })
};

🚀 Step 3: Actions and Reducers

// ⚡ Action types
type ActionType = 'SET_LOADING' | 'SET_DATA' | 'SET_ERROR';
type ActionMap = Record<ActionType, {
  slice: StateSlices;
  payload?: any;
}>;

// 🔄 State updater functions
type SliceUpdaters = Record<StateSlices, {
  setLoading: (loading: boolean) => void;
  setData: (data: any) => void;
  setError: (error: string | null) => void;
}>;

// ✨ Usage example
const updateUserData = (userData: UserData) => {
  // 🎯 Type-safe state updates
  const newState = {
    ...initialState,
    user: {
      ...initialState.user,
      data: userData,
      loading: false,
      lastUpdated: new Date()
    }
  };
  return newState;
};

🎓 Challenge Solution

Amazing! 🎉 You’ve created a type-safe state management system using Record. Notice how Record helped create consistent structure while maintaining type safety for each slice.

🎓 Key Takeaways

Fantastic work! 🎉 You’ve mastered the Record utility type. Here’s what you’ve learned:

🏆 Core Concepts

  • Record<K, V> 📊: Creates object types with consistent value types
  • K defines the allowed keys (union types work great!)
  • V ensures all values follow the same structure

💡 Best Practices

  • 🎯 Use Record for homogeneous data structures
  • 🔑 Combine with union types for precise key sets
  • 🏗️ Build reusable patterns with Record
  • 🔄 Use Partial<Record<K, V>> for optional keys

🚀 Real-World Applications

  • ⚙️ Configuration objects with consistent structure
  • 🗃️ State management with predictable slice types
  • 🌐 API response mappings and lookup tables
  • 🎨 Theme systems and UI configuration

🤝 Next Steps

Ready to explore more utility types? Here are your next adventures:

  1. Extract and Exclude 🔍: Filter union types precisely
  2. NonNullable 🛡️: Remove null and undefined from types
  3. Parameters and ReturnType 🔧: Extract function type information
  4. Mapped Types 🗺️: Transform existing types with advanced patterns

Keep building amazing, type-safe applications with Record! 🚀✨

You’re becoming a TypeScript utility type master! 🧙‍♂️📊