Record
typesIn 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.
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. Map
While 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, delete operator) |
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.
Record
The 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.
forEach
To 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...in
The 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 Record
The 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 Record
The 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.
Record
While 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 }>;
Record
Let’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 keysRecord
In 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 is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
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 nowExplore the MUI Grid system in depth, including updates from MUI v5, and learn how to build responsive grid layouts.
Build a progressive web app using Rust, WebAssembly, SurrealDB, and Nostr with local encryption and fast storage.
Explore various ways to implement SVGs in React applications, and learn about their integration, animation, and usage as React components.
Discover how AI code generation works and explore top artificial intelligence coding tools, benefits, and real-world use cases.
One Reply to "Level up your TypeScript with <code>Record</code> types"
The final examples never use CourseInfo interface?