Level up your TypeScript with Record types
Written by Matteo Di Pirro✏️ 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 = { [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. What is the Record type in TypeScript? The Record 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 represents an object where all keys are strings and all values are numbers, while Record 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 TypeScript Record vs. other key-value mappings When 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 objects At first glance, a Record type might seem similar to a regular object, but there are important differences: ```typescripttype RecordObject = Record; // 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](https://blog.logrocket.com/pattern-matching-type-safety-typescript/) of `Record` type. The `Record` type provides stronger type safety for keys, especially when used with union types or literals: ```typescripttype ValidKeys = 'id' | 'name' | 'age'; type UserRecord = Record; 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: ```typescripttype UserRecord = Record; const userRecord: UserRecord = { 'john': { age: 30, active: true }, 'jane': { age: 28, active: false } }; const userMap = new Map(); 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` `new Map()` 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 types TypeScript also offers indexed types, which provide another way to define key-value structures: ```typescript// Indexed type interface IndexedUser { [key: string]: { age: number, active: boolean }; } type RecordUser = Record; Index signature offers more flexibility than Record type

Written by Matteo Di Pirro✏️
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.
What is the Record
type in TypeScript?
The Record
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
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
TypeScript Record
vs. other key-value mappings
When 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 objects
At first glance, a Record
type might seem similar to a regular object, but there are important differences:
```typescripttype RecordObject = Record;
// 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](https://blog.logrocket.com/pattern-matching-type-safety-typescript/) of `Record` type. The `Record` type provides stronger type safety for keys, especially when used with union types or literals:
```typescripttype ValidKeys = 'id' | 'name' | 'age';
type UserRecord = Record;
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:
```typescripttype UserRecord = Record;
const userRecord: UserRecord = {
'john': { age: 30, active: true },
'jane': { age: 28, active: false }
};
const userMap = new Map();
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
`
`
`
`
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 types
TypeScript also offers indexed types, which provide another way to define key-value structures:
```typescript// Indexed type
interface IndexedUser {
[key: string]: { age: number, active: boolean };
}
type RecordUser = Record;
Index signature offers more flexibility than Record
type as it can be combined with explicit properties like a normal interface definition:
```typescriptinterface 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.
## Setting up and defining a `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](https://blog.logrocket.com/understanding-discriminated-union-intersection-types-typescript/) to specify the allowed keys. For example, we could use both types to model a university’s courses:
```typescripttype Course = "Computer Science" | "Mathematics" | "Literature"
interface CourseInfo {
professor: string
cfu: number
}
const courses: Record = {
"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
.
Identifying missing properties
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
.” In this example, TypeScript is clearly telling us that Literature
is missing.
Identifying undefined properties
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
.”
Accessing Record
data
We can access data related to each Course
as we would with any other dictionary:
The statement above prints the following output:
```typescript{ "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.
## Iterating over TypeScript record types
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.
### Using `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()`:
```typescripttype Course = "Computer Science" | "Mathematics" | "Literature";
interface CourseInfo {
professor: string;
cfu: number;
}
const courses: Record = {
"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}`);
});
Using for...in
The for...in
loop allows iterating over the keys of a record:
``typescriptfor (const key in courses) {
${key}: ${course.professor}, ${course.cfu}`);
if (courses.hasOwnProperty(key)) {
const course = courses[key as Course];
console.log(
}
}
### Using `Object.keys()`
`Object.keys()` returns an array of the record’s keys, which can then be iterated over using `forEach` or any loop:
```typescriptObject.keys(courses).forEach((key) => {
const course = courses[key as Course];
console.log(`${key}: ${course.professor}, ${course.cfu}`);
});
Using Object.values()
Object.values()
returns an array of the record’s values, which can be iterated over:
``typescriptObject.values(courses).forEach((course) => {
${course.professor}, ${course.cfu}`);
console.log(
});
### Using `Object.entries()`
`Object.entries()` returns an array of key-value pairs, allowing you to use array destructuring within the loop:
```typescriptObject.entries(courses).forEach(([key, value]) => {
console.log(`${key}: ${value.professor}, ${value.cfu}`);
});
Advanced use cases for TypeScript record types
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.
How to use the Pick
type with Record
for selective type mapping
The 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
:
```typescriptinterface CourseInfo {
professor: string;
cfu: number;
semester: string;
students: number;
}
type SelectedCourseInfo = Pick;
type Course = "Computer Science" | "Mathematics" | "Literature";
const courses: Record = {
"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` 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`.
### How to handle dynamic keys in TypeScript safely
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.
#### Using `Record` with string keys
The simplest approach out there is to use `Record` with `string` as the key type, while constraining the value types:
```typescripttype PreferenceValue = string | boolean | number;
type Preferences = Record;
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.
Union types for known keys with string fallback
This is for when you have some known keys but also need to allow arbitrary ones:
```typescript// 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;
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.
### Combining `Record` with other utility types
TypeScript’s utility types can be combined with `Record` to create more complex and type safe data structures.
### Using `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:
```typescripttype ReadonlyCourseInfo = Readonly;
const readonlyCourses: Record = {
"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
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:
```typescriptreadonlyCourses["Computer Science"].cfu = 14;
// Error: Cannot assign to 'cfu' because it is a read-only property.
### Using `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:
```typescripttype PartialCourseInfo = Partial;
const partialCourses: Record = {
"Computer Science": { professor: "Mary Jane" },
"Mathematics": { cfu: 12 },
"Literature": {},
};
In the code above, Partial
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.
Performance considerations and when NOT to use 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.
Runtime performance
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:
```typescript// Deep nesting
type DeepRecord = Record>>;
// Flatter structure
type FlatRecord = Record;
### When to avoid using `Record`
Let’s consider some scenarios where using `Record` might not be the best choice:
* **Unpredictable keys** — Say your keys are unpredictable, meaning they are generated at runtime or sort. In this case, a `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 time
* **When frequent additions and removals are needed** — In this case, `Map` provides better performance for frequent mutations than `Record`, given that it’s optimized for stable and less frequently changing data
* **When dealing with non-string keys** — This is another scenario where you should use `Map`. `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 keys
* **Handling large datasets constantly** — With a large dataset (very large collections with thousands of entries), specialized data structures might offer better performance than the use of `Record`
## Conclusion
In this article, we discussed TypeScript's built-in `Record` 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](https://lp.logrocket.com/blg/typescript-signup?utm_source=devto&utm_medium=organic&utm_campaign=25Q2&utm_content=typescript-record-types): Full visibility into your web and mobile apps
[
](https://lp.logrocket.com/blg/typescript-signup?utm_source=devto&utm_medium=organic&utm_campaign=25Q2&utm_content=typescript-record-types)
[LogRocket](https://lp.logrocket.com/blg/typescript-signup?utm_source=devto&utm_medium=organic&utm_campaign=25Q2&utm_content=typescript-record-types) 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.
[Try it for free](https://lp.logrocket.com/blg/typescript-signup?utm_source=devto&utm_medium=organic&utm_campaign=25Q2&utm_content=typescript-record-types).