623

What does Record<K, T> mean in Typescript?

Typescript 2.1 introduced the Record type, describing it in an example:

// For every properties K of type T, transform it to U
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>

see Typescript 2.1

And the Advanced Types page mentions Record under the Mapped Types heading alongside Readonly, Partial, and Pick, in what appears to be its definition:

type Record<K extends string, T> = {
    [P in K]: T;
}

Readonly, Partial and Pick are homomorphic whereas Record is not. One clue that Record is not homomorphic is that it doesn’t take an input type to copy properties from:

type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

And that's it. Besides the above quotes, there is no other mention of Record on typescriptlang.org.

Questions

  1. Can someone give a simple definition of what Record is?

  2. Is Record<K,T> merely a way of saying "all properties on this object will have type T"? Probably not all properties, since K has some purpose...

  3. Does the K generic forbid additional keys on the object that are not K, or does it allow them and just indicate that their properties are not transformed to T?

  4. With the given example:

    type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
    

    Is it exactly the same as this?:

    type ThreeStringProps = {prop1: string, prop2: string, prop3: string}
    
5
  • 36
    The answer to 4. is pretty much "yes", so that should probably answer your other questions.
    – jcalz
    Commented Aug 20, 2018 at 18:30
  • At the moment of writing the docs page regarding Record is been deprecated and in the new one there is not even mention to those types Commented Jun 17, 2022 at 7:44
  • 1
    @CarmineTambascia do you have a link? The old documentation site is deprecated but I don't see anything about Record being deprecated.
    – Matthias
    Commented Jun 20, 2022 at 1:40
  • @Matthias I mean the same thing as you, not that Record is deprecated. It isn't mentioned anymore, as long I can see. Commented Jun 20, 2022 at 5:22
  • 4
    It is now listed in the Utility Types section: typescriptlang.org/docs/handbook/…
    – Flip
    Commented Sep 29, 2022 at 13:30

4 Answers 4

666
  1. Can someone give a simple definition of what Record is?

A Record<K, T> is an object type whose property keys are K and whose property values are T. That is, keyof Record<K, T> is equivalent to K, and Record<K, T>[K] is (basically) equivalent to T.

  1. Is Record<K,T> merely a way of saying "all properties on this object will have type T"? Probably not all objects, since K has some purpose...

As you note, K has a purpose... to limit the property keys to particular values. If you want to accept all possible string-valued keys, you could do something like Record<string, T>, but the idiomatic way of doing that is to use an index signature like { [k: string]: T }.

  1. Does the K generic forbid additional keys on the object that are not K, or does it allow them and just indicate that their properties are not transformed to T?

It doesn't exactly "forbid" additional keys: after all, a value is generally allowed to have properties not explicitly mentioned in its type... but it wouldn't recognize that such properties exist:

declare const x: Record<"a", string>;
x.b; // error, Property 'b' does not exist on type 'Record<"a", string>'

and it would treat them as excess properties which are sometimes rejected:

declare function acceptR(x: Record<"a", string>): void;
acceptR({a: "hey", b: "you"}); // error, Object literal may only specify known properties

and sometimes accepted:

const y = {a: "hey", b: "you"};
acceptR(y); // okay
  1. With the given example:

     type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
    

    Is it exactly the same as this?:

     type ThreeStringProps = {prop1: string, prop2: string, prop3: string}
    

Yes!

9
  • 16
    Very much learned and a question why "the idiomatic way of doing that is to use an index signature" not a Record one ? I don't find any related information about this "idiomatic way".
    – legend80s
    Commented Oct 28, 2019 at 6:55
  • 26
    You can use Record<string, V> to mean {[x: string]: V} if you want; I've probably even done this myself. The index signature version is more direct: they are the same type, but the former is a type alias of a mapped type which evaluates to an index signature, while the latter is just the index signature directly. All else being equal, I'd recommend the latter. Similarly I wouldn't use Record<"a", string> in place of {a: string} unless there were some other compelling contextual reason do to so.
    – jcalz
    Commented Oct 28, 2019 at 13:58
  • 5
    "All else being equal, I'd recommend the latter." Why is that? My pre-Typescript self agrees, but I know the former will be more, um, self-commenting for people coming from the C# side, for instance, and no worse for JavaScript-to-Typescripters. Are you just interested in skipping the transpilation step for those constructs?
    – ruffin
    Commented Nov 14, 2019 at 14:08
  • 12
    Just my opinion: the behavior of Record<string, V> only makes sense if you already know how index signatures work in TypeScript. E.g., given x: Record<string, string>, x.foo will apparently be a string at compile time, but in actuality is likely to be string | undefined. This is a gap in how --strictNullChecks works (see #13778). I'd rather have newcomers deal with {[x: string]: V} directly instead of expecting them to follow the chain from Record<string, V> through {[P in string]: V} to the index signature behavior.
    – jcalz
    Commented Nov 14, 2019 at 14:27
  • 2
    @KishanVaishnav no, it's just a plain object type with keys an values. Sounds like you'd rather use something like Map instead.
    – jcalz
    Commented Jun 22, 2020 at 15:18
307

A Record lets you create a new type from a Union. The values in the Union are used as attributes of the new type.

For example, say I have a Union like this:

type CatNames = "miffy" | "boris" | "mordred";

Now I want to create an object that contains information about all the cats, I can create a new type using the values in the CatNames union as keys.

type CatList = Record<CatNames, {age: number}>

If I want to satisfy this CatList, I must create an object like this:

const cats: CatList = {
  miffy: { age:99 },
  boris: { age:16 },
  mordred: { age:600 }
}

You get very strong type safety:

  • If I forget a cat, I get an error.
  • If I add a cat that's not allowed, I get an error.
  • If I later change CatNames, I get an error. This is especially useful because CatNames is likely imported from another file, and likely used in many places.

Real-world React example.

I used this recently to create a Status component. The component would receive a status prop, and then render an icon. I've simplified the code quite a lot here for illustrative purposes

I had a union like this:

type Statuses = "failed" | "complete";

I used this to create an object like this:

const icons: Record<
  Statuses,
  { iconType: IconTypes; iconColor: IconColors }
> = {
  failed: {
    iconType: "warning",
    iconColor: "red"
  },
  complete: {
    iconType: "check",
    iconColor: "green"
  };

I could then render by destructuring an element from the object into props, like so:

const Status = ({status}) => <Icon {...icons[status]} />

If the Statuses union is later extended or changed, I know my Status component will fail to compile and I'll get an error that I can fix immediately. This allows me to add additional error states to the app.

Note that the actual app had dozens of error states that were referenced in multiple places, so this type safety was extremely useful.

Docs

Wonderful to see that my silly little cat example is now part of the official TypeScript docs!

https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type

9
  • I assume most of the time type Statuses lives in typings NOT defined by you? Otherwise I can see something like an interface with an enum being a better fit right? Commented Jun 17, 2019 at 14:56
  • Hi @victorio, I'm not sure how an enum would solve the problem, you don't get an error in an enum if you miss a key. It's just a mapping between keys and values. Commented Jun 18, 2019 at 8:37
  • 2
    I see what you mean now. Coming from C# we do not have clever ways to do that. The closest thing would be a dictionary of Dictionary<enum, additional_metadata>. The Record type is a great way to represent that enum + metadata pattern. Commented Jun 18, 2019 at 16:52
  • Is there a way to initialize a record that has several keys mapped to a single value? Let's say I have 10 different statuses but in the UI all of them should be shown with a warning icon. If I go with record type, I'll have to duplicate the same 10 times, which I would be able to avoid if I use something like a switch statement. Commented May 7, 2021 at 13:48
  • 1
    Well, but const bla: CatNames = "miffy"; works. TS describes the | as a union type, so only one is needed, but the record needs all. Very confusing. JS with TS is still a mess. Commented Apr 1, 2022 at 13:50
10

There is now a slightly longer documentation of the Record type: https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type

Quoting that:

Record<Keys, Type> Released: 2.1

Constructs an object type whose property keys are Keys and whose property values are Type. This utility can be used to map the properties of a type to another type.

interface CatInfo {
  age: number;
  breed: string;
}
 
type CatName = "miffy" | "boris" | "mordred";
 
const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};
 
cats.boris;
 
const cats: Record<CatName, CatInfo>
4

simple example of using Record :

let say we have the following object and we want to define an object type for it.

const networkConfig = {
 4: {
    name: "rinkeby",
    ethUsdPriceFeed: "0x8A753747A1Fa494EC906cE90E9f37563A8AF630e",
  }
}

So the type will be like the following one using Record :

type networkConfigType = Record<number, {
    name: string;
    ethUsdPriceFeed: string;
}>;

2
  • what difference between type networkConfigType = { [key: number]: { name: string; ethUsdPriceFeed: string; } };
    – arkan4ik92
    Commented Oct 18, 2023 at 8:23
  • 1
    Both code snippets define the same type for an object with numeric keys and a specific structure. Your suggestion is a more explicit way to define it, while mine uses the Record utility type for brevity. Functionally, they are equivalent. Commented Oct 20, 2023 at 14:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.