Prevent Type Widening of Object Literals with TypeScript's const Assertions

Marius Schulz
InstructorMarius Schulz

Share this video with your friends

Send Tweet
Published a year ago
Updated a year ago

A const assertion is a special type assertion that uses the const keyword instead of a specific type name. When using a const assertion on an object literal expression, all properties will become readonly properties and no literal types within the expression will be widened.

Additional Reading

Instructor: Here I have to find the origin point which has x and y coordinates of zero. I've chosen an all uppercase name to signal that we do not want to be changing these values. Origin is meant to be a constant.

However, typescript will happily let us mutate the properties of this object. This is because the object is inferred to have two mutable properties x and y both of type number. What we actually want is two read-only properties.

However, we cannot go in here and add the read-only modifier because this would not be valid syntax. Instead we have to add a type annotation to the object. Within this object type annotation, we can now declare two read-only properties for our point.

With this type annotation in place, typescript is now telling us that we cannot assign to x because it is now a read-only property. So far, so good, but if you look at our code, you can see that most of it is now our type annotation. It is quite verbose.

There is an alternative way of typing this object here, and that is using a const assertion. Let me go ahead and delete this type annotation and add the type assertion as const. This const assertion does a few things for us.

First of all, when we use it on an object literal, like we are doing here, TypeScript is inferring every property to be read only. Because of that, we still get the same error saying that we cannot assign to x because it is a read-only property.

Also notice that both of our properties do not have the type number. Both x and y have the numeric literal type zero. This means that they can only contain the value zero, rather than any arbitrary number. When we use a const assertion, TypeScript will not widen our literal types.

What this means is that we are not going from the value zero to the type number. Instead, TypeScript is inferring the most specific type possible. In this case, that is the numeric literal type zero for both of our properties.

Keep in mind that const assertions are a feature of TypeScript's type system. They don't have any runtime manifestation, so at runtime, we will still be able to change the properties of our object. If we wanted the entire object to be frozen and immutable, we could call the Object.freeze() method on it. Now, the assignment in line six jar x property would fail at runtime.

Andrew Ross
Andrew Ross
~ a year ago

ah, so the Object.freeze() method is a wrapper equivalent to the following?

const ORIGIN: Readonly<{ readonly x: 0; readonly y: 0; }> = { x: 0, y: 0 };

Marius Schulz
Marius Schulzinstructor
~ a year ago

@Andrew: Not quite. TypeScript's readonly modifier does not have any runtime manifestation — it's completely compiled away, so ORIGIN will have two mutable properties x and y.

Object.freeze(), on the other hand, freezes an object at runtime. When an object is frozen, it can no longer be changes. Fro example, property values can't be changed, and properties can't be removed, added, or reconfigured.

So readonly gives you some protection through type-checking at compile-time, whereas Object.freeze() actually prevents modifications at runtime.