Record types
In TypeScript, the Record type simply allows you to define dictionary-like objects present in languages like Python. It is a key-value pair structure, with a fixed type for the keys and a generic type for the values as well. Here is the internal definition of the Record type:
type Record<K extends string | number | symbol, T> = { [P in K]: T; }
In this article, we’ll explore the Record type in TypeScript to better understand what it is and how it works. We’ll also see how to use it to handle enumeration, and how to use it with generics to understand the properties of the stored value when writing reusable code.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Record type in TypeScript?The Record<Keys, Type> is a utility type in TypeScript that helps define objects with specific key-value pairs. It creates an object type where the property keys are of type Keys, and the values are of type Type. This is particularly useful when you need to ensure that an object has a specific structure with predefined keys and value types.
In practical terms, Record is often used to represent collections of data, such as API responses, configuration objects, or dictionaries. For example, Record<string, number> represents an object where all keys are strings and all values are numbers, while Record<'id' | 'name' | 'age', string> represents an object that must have exactly the properties id, name, and age, all with string values.
The primary benefit of using Record is type safety – TypeScript will verify that all required keys are present and that all values match their expected types, catching potential errors at compile time rather than runtime.
Editor’s note: This article was last updated by Ikeh Akinyemi in April 2025 to align with updates to TypeScript 5.8.3, add comparisons between Record and other object-mapping approaches, and discuss performance and real-world applications of Record
Record vs. other key-value mappingsWhen working with key-value pairs in TypeScript, several options are available, each with distinct characteristics and use cases. Understanding the differences between Record, plain objects, Map, and indexed types will help you choose the right approach for your specific needs.
Record vs. plain objectsAt first glance, a Record type might seem similar to a regular object, but there are important differences:
type RecordObject = Record<string, number>;
// Usage is similar
const plainObj = { a: 1, b: 2 };
const recordObj: RecordObject = { a: 1, b: 2 };
The usage of the two types is similar. Their difference lies in the type safety of Record type. The Record type provides stronger type safety for keys, especially when used with union types or literals:
type ValidKeys = 'id' | 'name' | 'age';
type UserRecord = Record<ValidKeys, string | number>;
const user: UserRecord = {
id: 10,
name: 'Peter',
age: 37,
// address: '123 Main St' // Error: Object literal may only specify known properties
};
Record vs. MapWhile both Record and JavaScript’s built-in [Map](https://blog.logrocket.com/typescript-mapped-types/) can store key-value pairs, they serve different purposes and have distinct characteristics:
type UserRecord = Record<string, { age: number, active: boolean }>;
const userRecord: UserRecord = {
'john': { age: 30, active: true },
'jane': { age: 28, active: false }
};
const userMap = new Map<string, { age: number, active: boolean }>();
userMap.set('john', { age: 30, active: true });
userMap.set('jane', { age: 28, active: false });
The following table compares TypeScript Record and Map types across different features:
Record |
Map |
|
|---|---|---|
| Performance | Optimized for static data access; faster for direct property access | Optimized for frequent additions and removals; slightly slower for lookups |
| Type safety | Strong compile-time type checking for both keys and values | Runtime type safety; any type can be used as keys, including objects and functions |
| Use cases | Ideal for static dictionaries with predetermined keys | Better for dynamic collections where keys/values change frequently |
| Syntax |
Record<KeyType, ValueType> |
new Map<KeyType, ValueType>() |
| Key types | Limited to string, number, or symbol (which convert to strings) |
Supports any data type as keys, including objects and functions |
| Methods | Standard object operations (dot notation, brackets, deleteoperator) |
Built-in methods: get(), set(), has(), delete(), clear(), size |
| Iteration | No guaranteed order (though modern engines maintain creation order) | Preserves insertion order when iterating |
| Memory | Better memory efficiency for static data | Higher memory overhead but better for frequently changing collections |
Record vs. indexed typesTypeScript also offers indexed types, which provide another way to define key-value structures:
// Indexed type
interface IndexedUser {
[key: string]: { age: number, active: boolean };
}
type RecordUser = Record<string, { age: number, active: boolean }>;
Index signature offers more flexibility than Record type as it can be combined with explicit properties like a normal interface definition:
interface MixedInterface {
id: number; // Explicit property
name: string; // Explicit property
[key: string]: any; // Any other properties
};
You will mostly use the indexed type signature when you need to mix specific properties with dynamic properties, or you have relaxed and simpler typing requirements.
RecordThe power of TypeScript’s Record type is that we can use it to model dictionaries with a fixed number of keys, as we have seen earlier. This involves the use of the union type to specify the allowed keys. For example, we could use both types to model a university’s courses:
type Course = "Computer Science" | "Mathematics" | "Literature"
interface CourseInfo {
professor: string
cfu: number
}
const courses: Record<Course, CourseInfo> = {
"Computer Science": {
professor: "Mary Jane",
cfu: 12
},
"Mathematics": {
professor: "John Doe",
cfu: 12
},
"Literature": {
professor: "Frank Purple",
cfu: 12
}
}
In this example, we defined a union type named Course that will list the names of classes and an interface type named CourseInfo that will hold some general details about the courses. Then, we used a Record type to match each Course with its CourseInfo.
So far, so good — it all looks like quite a simple dictionary. The real strength of the Record type is that TypeScript will detect whether we missed a Course.
Let’s say we didn’t include an entry for Literature. We’d get the following error at compile time:
“Property Literature is missing in type { "Computer Science": { professor: string; cfu: number; }; Mathematics: { professor: string; cfu: number; }; } but required in type Record<Course, CourseInfo>.”
In this example, TypeScript is clearly telling us that Literature is missing.
TypeScript will also detect if we add entries for values that are not defined in Course. Let’s say we added another entry in Course for a History class. Because we didn’t include History as a Course type, we’d get the following compilation error:
“Object literal may only specify known properties, and "History" does not exist in type Record<Course, CourseInfo>.”
Record dataWe can access data related to each Course as we would with any other dictionary:
console.log(courses["Literature"])
The statement above prints the following output:
{ "teacher": "Frank Purple", "cfu": 12 }
Next, let’s take a look at some ways to iterate over the keys of a Record type as a data collection.
In this section, we will explore various methods to iterate over Record types, including forEach, for...in, Object.keys(), and Object.values(). Understanding how to iterate over TypeScript Record types is crucial for effectively accessing the data within these structures.
forEachTo use forEach with a Record type, you first need to convert the Record to an array of key-value pairs. This can be done using Object.entries():
type Course = "Computer Science" | "Mathematics" | "Literature";
interface CourseInfo {
professor: string;
cfu: number;
}
const courses: Record<Course, CourseInfo> = {
"Computer Science": { professor: "Mary Jane", cfu: 12 },
"Mathematics": { professor: "John Doe", cfu: 12 },
"Literature": { professor: "Frank Purple", cfu: 12 },
};
Object.entries(courses).forEach(([key, value]) => {
console.log(`${key}: ${value.professor}, ${value.cfu}`);
});
for...inThe for...in loop allows iterating over the keys of a record:
for (const key in courses) {
if (courses.hasOwnProperty(key)) {
const course = courses[key as Course];
console.log(`${key}: ${course.professor}, ${course.cfu}`);
}
}
Object.keys()Object.keys() returns an array of the record’s keys, which can then be iterated over using forEach or any loop:
Object.keys(courses).forEach((key) => {
const course = courses[key as Course];
console.log(`${key}: ${course.professor}, ${course.cfu}`);
});
Object.values()Object.values() returns an array of the record’s values, which can be iterated over:
Object.values(courses).forEach((course) => {
console.log(`${course.professor}, ${course.cfu}`);
});
Object.entries()Object.entries() returns an array of key-value pairs, allowing you to use array destructuring within the loop:
Object.entries(courses).forEach(([key, value]) => {
console.log(`${key}: ${value.professor}, ${value.cfu}`);
});
TypeScript’s Record type can be utilized for more advanced patterns, such as selective type mapping with the Pick type and implementing dynamic key-value pairs. These use cases provide additional flexibility and control when working with complex data structures.
Pick type with Record for selective type mappingThe Pick type in TypeScript allows us to create a new type by selecting specific properties from an existing type. When combined with the Record type, it becomes a powerful tool for creating dictionaries with only a subset of properties.
Suppose we have a CourseInfo interface with several properties, but we only want to map a subset of these properties in our Record:
interface CourseInfo {
professor: string;
cfu: number;
semester: string;
students: number;
}
type SelectedCourseInfo = Pick<CourseInfo, "professor" | "cfu">;
type Course = "Computer Science" | "Mathematics" | "Literature";
const courses: Record<Course, SelectedCourseInfo> = {
"Computer Science": { professor: "Mary Jane", cfu: 12 },
"Mathematics": { professor: "John Doe", cfu: 12 },
"Literature": { professor: "Frank Purple", cfu: 12 },
};
In the above example, we used Pick<CourseInfo, "professor" | "cfu"> to create a new type SelectedCourseInfo that only includes the professor and the cfu properties from CourseInfo. Then, we defined a Record type that maps each Course to SelectedCourseInfo.
There’s the common question of how to deal with dynamic or unknown keys while ensuring type safety at runtime. How do we type user-generated objects, or data coming in from external APIs with structures that you’re not certain of beforehand?
The Record type provides a couple of patterns for handling this common scenario effectively.
Record with string keysThe simplest approach out there is to use Record with string as the key type, while constraining the value types:
type PreferenceValue = string | boolean | number;
type Preferences = Record<string, PreferenceValue>;
const userPreferences: Preferences = {};
function setPreference(key: string, value: PreferenceValue) {
userPreferences[key] = value;
}
setPreference('theme', 'dark');
setPreference('notifications', true);
// setPreference('fontSize', []); // Error: Type 'never[]' is not assignable to type 'PreferenceValue'
This approach provides type safety for the union values while allowing any string to be used as the key.
This is for when you have some known keys but also need to allow arbitrary ones:
// Define known keys
type KnownPreferenceKeys = 'theme' | 'notifications' | 'fontSize';
// Create a type for known and unknown keys
type PreferenceKey = KnownPreferenceKeys | string;
type PreferenceValue = string | boolean | number;
type Preferences = Record<PreferenceKey, PreferenceValue>;
const preferences: Preferences = {
theme: 'dark',
notifications: true,
fontSize: 14,
// Allow additional properties
customSetting: 'value'
};
This approach provides better autocompletion for known keys while still allowing arbitrary string keys.
Record with other utility typesTypeScript’s utility types can be combined with Record to create more complex and type safe data structures.
ReadOnly with RecordThe ReadOnly type makes all the properties of a type read-only. This is especially useful when you want to ensure that dictionary entries cannot be modified:
type ReadonlyCourseInfo = Readonly<CourseInfo>;
const readonlyCourses: Record<Course, ReadonlyCourseInfo> = {
"Computer Science": { professor: "Mary Jane", cfu: 12, semester: "Fall", students: 100 },
"Mathematics": { professor: "John Doe", cfu: 12, semester: "Spring", students: 80 },
"Literature": { professor: "Frank Purple", cfu: 12, semester: "Fall", students: 60 },
};
// Trying to modify a readonly property will result in a compile-time error
// readonlyCourses["Computer Science"].cfu = 14; // Error: Cannot assign to 'cfu' because it is a read-only property.
In the code above, Readonly<CourseInfo> ensures that all properties of CourseInfo are read-only, preventing modifications. Trying to modify a read-only property will result in a compile-time error. The below should fail/throw an error:
readonlyCourses["Computer Science"].cfu = 14; // Error: Cannot assign to 'cfu' because it is a read-only property.
Partial with RecordThe Partial type makes all properties of a type optional. This is especially useful when you want to create a dictionary where some entries may not have all properties defined:
type PartialCourseInfo = Partial<CourseInfo>;
const partialCourses: Record<Course, PartialCourseInfo> = {
"Computer Science": { professor: "Mary Jane" },
"Mathematics": { cfu: 12 },
"Literature": {},
};
In the code above, Partial<CourseInfo> makes all properties of CourseInfo optional, allowing us to create a Record where some courses may not have all properties defined or even have none of the properties defined.
RecordWhile you can often use TypeScript’s Record type, there are times when it is not the ideal solution. You must understand its performance characteristics and limitations to make better design decisions while using it.
Record type is primarily a compile-time type construct with no runtime overhead; at runtime, a Record is just a JavaScript object.
However, how you structure and use it can impact application performance. Consider, for example, that direct property access (obj.propName) is relatively faster than dynamic access (obj[propName]).
Also, Record objects with a large number of entries can consume significant memory and, when nesting, choose flatter data structures over deeply nested Record types:
// Deep nesting
type DeepRecord = Record<string, Record<string, Record<string, string>>>;
// Flatter structure
type FlatRecord = Record<string, { category: string; subcategory: string; value: string }>;
RecordLet’s consider some scenarios where using Record might not be the best choice:
Map might be more appropriate than using a Record type. Record works best when you can define the shape of your data structure at compile timeMap provides better performance for frequent mutations than Record, given that it’s optimized for stable and less frequently changing dataMap. Record keys are limited to string, number, or symbol types (which are converted to strings), but Map supports the use of objects, functions, or other non-primitive values as keysRecordIn this article, we discussed TypeScript’s built-in Record<K, V> utility type. We examined its basic usage and behavior, comparing it with other key-value structures like plain objects and Maps to understand when Record provides the most value.
We discussed various approaches for iterating over TypeScript record types using methods such as forEach, for...in, Object.keys(), and Object.values(), which enable effective manipulation and access to data within these structures.
The article also covered advanced patterns, showing how to combine Record with TypeScript’s other utility types like Pick, Partial, and Readonly to create more sophisticated and type-safe data structures. We talked about practical applications, including handling dynamic keys and creating selective type mappings.
Finally, we reviewed performance considerations and scenarios where alternative approaches might be more appropriate than using the Record type.
The Record type is a very useful type in the TypeScript. While some use cases may be specialized, it offers significant value for creating safe, maintainable code in many applications. How do you use TypeScript “Record in your projects? Let us know in the comments!
LogRocket lets you replay user sessions, eliminating guesswork by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks, and with plugins to log additional context from Redux, Vuex, and @ngrx/store.
With Galileo AI, you can instantly identify and explain user struggles with automated monitoring of your entire product experience.
Modernize how you understand your web and mobile apps — start monitoring for free.

Vibe coding isn’t just AI-assisted chaos. Here’s how to avoid insecure, unreadable code and turn your “vibes” into real developer productivity.

GitHub SpecKit brings structure to AI-assisted coding with a spec-driven workflow. Learn how to build a consistent, React-based project guided by clear specs and plans.

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
One Reply to "Level up your TypeScript with <code>Record</code> types"
The final examples never use CourseInfo interface?