Understanding 'as const' in TypeScript

avatar

Borislav Hadzhiev

Last updated: Feb 16, 2022

banner

Photo from Unsplash

Understanding 'as const' in TypeScript #

When using as const in TypeScript, we are able to set the properties of an object or the elements of an array to readonly, indicating to the language, that the type in the expression will not be widened (e.g. from 42 to number).

index.ts
function sum(a: number, b: number) { return a + b; } // 👇️ const arr: readonly [3, 4] const arr = [3, 4] as const; console.log(sum(...arr)); // 👉️ 7

We created a sum function that takes 2 numbers as parameters and returns the sum.

However, we have the numbers in an array, so we need a way to tell TypeScript that this array can only ever have 2 elements of type number.

This is when you would use a const assertion in TypeScript.

The const assertion enables us to tell TypeScript that the array's type will not be widened, e.g. from [3, 4] to number[].

The array in the example becomes a readonly tuple, so its contents cannot be changed and we can safely unpack the two numbers in a call to the sum function.

If you try to change the contents of the array, you would get an error.

index.ts
function sum(a: number, b: number) { return a + b; } // 👇️ const arr: readonly [3, 4] const arr = [3, 4] as const; // ⛔️ ERROR: Property 'push' does not exist on type // 'readonly [3, 4]' arr.push(5);

We used a const assertion, so the array is now a readonly tuple, whose contents cannot be changed and attempting to do so would cause an error during development.

If we try to call the sum function without using a const assertion, we would get an error.

index.ts
function sum(a: number, b: number) { return a + b; } // 👇️ const arr: readonly [3, 4] const arr = [3, 4]; // ⛔️ Error: A spread argument must either have a tuple // type or be passed to a rest parameter. console.log(sum(...arr)); // 👉️ 7

TypeScript warns us that there is no way of knowing that the contents of the arr variable have not changed between its declaration and the time the sum() function was called.

When a const assertion is used, the array becomes a readonly tuple, so TypeScript can be sure that its contents will not change between its declaration and the function invocation.

You can also use const assertions as a replacement for enums if you don't like using TypeScript's enum construct.

index.ts
// 👇️ const Pages: {readonly home: '/'; readonly about: '/about'...} export const Pages = { home: '/', about: '/about', contacts: '/contacts', } as const;

If you try to change any of the properties of the object or add a new property, you would get an error.

index.ts
// 👇️ const Pages: {readonly home: '/'; readonly about: '/about'...} export const Pages = { home: '/', about: '/about', contacts: '/contacts', } as const; // ⛔️ Error: Cannot assign to 'about', because it is // a read-only property Pages.about = 'hello'; // ⛔️ Error: Property 'test' does not exist on type ... Pages.test = 'hello';

It should be noted, however, that const contexts do not convert an expression to be fully immutable.

index.ts
const arr = ['/about', '/contacts']; // 👇️ const Pages: {readonly home: '/', menu: string[]} export const Pages = { home: '/', menu: arr, } as const; // ✅ Works Pages.menu.push('/test');

The menu property references an external array, whose contents we are able to change.

If the array was defined in place, on the object, we wouldn't be able to change its contents.

index.ts
// 👇️ const Pages: {readonly home: '/', readonly menu: string[]} export const Pages = { home: '/', menu: ['/about'], } as const; // ⛔️ Error: Property 'push' does not exist on type // 'readonly ["/about"]' Pages.menu.push('/test');
I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.