Skip to content
Theory · Arrays and tuples · lesson 6 of 7

as const

Remember the sealed const jar? TypeScript writes its exact contents on the label: const limit = 5 gets the literal type 5. With an array that does not happen: even in a const, its elements get a general type.

Let's see where this gets in the way. A function accepts only sizes from a fixed list, and the sizes are kept in an array:

TypeScript
type Size = "S" | "M" | "L";

function order(size: Size) {
    console.log(size);
}

const sizes = ["S", "M", "L"];
order(sizes[0]); // ✗
TEXT
error TS2345: Argument of type 'string' is not assignable to parameter of type 'Size'.

sizes has the type string[]. An array in a const can still be changed (the write sizes[0] = "XXL" is allowed), so TypeScript widens "S", "M" and "L" to string, as it does for a let. And string does not fit the Size parameter.

How as const works

Writing as const after a literal tells TypeScript: this value will not change, describe it exactly. Let's add it to the array:

TypeScript
type Size = "S" | "M" | "L";

function order(size: Size) {
    console.log(size);
}

const sizes = ["S", "M", "L"] as const;
order(sizes[0]); // S

Now sizes has the type readonly ["S", "M", "L"], and the call compiles. This type changes three things at once:

  • the literal types are kept: position 0 holds "S", not string;
  • the array became a tuple: it has exactly three positions, each with its own type;
  • everything became read-only: tsc lets through neither sizes.push("XL") nor sizes[0] = "M".

To push, the compiler answers as it does for any read-only array:

TEXT
error TS2339: Property 'push' does not exist on type 'readonly ["S", "M", "L"]'.

It is like a row of lockers sealed at the factory: each door is labelled with its exact contents, and none of them can be opened.

as const on an object

as const works on an object literal too:

TypeScript
const config = { mode: "dark", retries: 3 } as const;
console.log(config.mode, config.retries); // dark 3

Without as const, config would have the type { mode: string; retries: number; }. With it, the type is { readonly mode: "dark"; readonly retries: 3; }:

  • every property became readonly, and config.retries = 4 gives error TS2540;
  • every property has a literal type: "dark" and 3.

as const works all the way down. { sizes: ["S", "M"] } as const has the type { readonly sizes: readonly ["S", "M"]; }: both the property and the array inside it are read-only. The readonly modifier on a property cannot do that: it protects one level.

Here the sealed-lockers analogy stops working. They are sealed only for tsc: as const is erased, and the JavaScript file keeps const sizes = ["S", "M", "L"];, an ordinary array that can be changed at run time.

When as const helps

as const is needed where a set of values is known in advance and does not change:

  • a list of options to choose from: sizes, roles, menu items;
  • settings the program only reads;
  • values that are later passed to parameters of literal types, as in order(sizes[0]).

There is another way too: an annotation. With const sizes: Size[] = ["S", "M", "L"]; the call order(sizes[0]) compiles as well. But this is an ordinary array: it can be changed, and its length can be anything. as const fixes both the values and the length.

An as const array is read-only, so it cannot be passed to a parameter of type string[]. Such a parameter is written as readonly string[].

Interviewers like to ask how const differs from as const. const forbids putting a new value into the name. as const describes the value itself: it keeps its literals and forbids changing its contents.

Common mistake

as const was added to a variable instead of a literal:

TypeScript
const sizes = ["S", "M", "L"];
const fixed = sizes as const; // ✗
TEXT
error TS1355: A 'const' assertion can only be applied to references to enum members, or string, number, boolean, array, or object literals.

tsc answers that as const applies only to literals (string, number, boolean, array and object literals) and to enum members. By the second line, the type of sizes has already been inferred as string[], and the literals "S", "M", "L" cannot be got back from it. Write as const right after the literal: const sizes = ["S", "M", "L"] as const;.

Key points

  • as const after a literal keeps the literal types and makes the value read-only.
  • An array with as const becomes a read-only tuple: ["S", "M"] as const is readonly ["S", "M"].
  • An object with as const has all its properties readonly and of literal types, all the way down.
  • as const goes on the literal itself, and only tsc checks it.