332

I am confused about the as const cast. I checked a few documents and videos but did not understand it fully.

My concern is what does the as const mean in the code below and what is the benefit of using it?

const args = [8, 5] as const;
const angle = Math.atan2(...args);
console.log(angle);
Cihat Şaman
  • 3,674
  • 4
  • 14
  • 24
  • 1
    They're **const**ants. They cannot be changed after they've been declared. It's the same was as `1` will always be `one (1)` in maths, numbers are constants because they cannot be changed. – Nora Apr 07 '21 at 19:56
  • 3
    See: https://dev.to/adamcoster/the-typescript-as-const-trick-2f4o – Terry Apr 07 '21 at 19:57
  • 1
    Possible duplicate of [What's the difference between TypeScript const assertions and declarations?](https://stackoverflow.com/questions/55230653/whats-the-difference-between-typescript-const-assertions-and-declarations) – jcalz Apr 07 '21 at 20:02
  • 3
    Have you read [the documentation](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions)? – axiac Apr 07 '21 at 22:26
  • 2
    @jcalz I think we can leave this open and let this question become that canonical for “what is as const”. Your answer here is really great – Linda Paiste Apr 08 '21 at 18:24
  • Up to date documentation: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-inference – Pere Jul 01 '23 at 22:06

6 Answers6

491

This is known as a const assertion. A const assertion tells the compiler to infer the narrowest* or most specific type it can for an expression. If you leave it off, the compiler will use its default type inference behavior, which will possibly result in a wider or more general type.

Note that it is called an "assertion" and not a "cast". The term "cast" is generally to be avoided in TypeScript; when people say "cast" they often imply some sort of effect that can be observed at runtime, but TypeScript's type system, including type assertions and const assertions, is completely erased from the emitted JavaScript. So there is absolutely no difference at runtime between a program that uses as const and one that does not.


At compile time, though, there is a noticeable difference. Let's see what happens when you leave out as const in the above example:

const args = [8, 5];
// const args: number[]
const angle = Math.atan2(...args); // error! Expected 2 arguments, but got 0 or more.
console.log(angle);

The compiler sees const args = [8, 5]; and infers the type of number[]. That's a mutable array of zero or more elements of type number. The compiler has no idea how many or which elements there are. Such an inference is generally reasonable; often, array contents are meant to be modified in some way. If someone wants to write args.push(17) or args[0]++, they'll be happy with a type of number[].

Unfortunately the next line, Math.atan2(...args), results in an error. The Math.atan2() function requires exactly two numeric arguments. But all the compiler knows about args is that it's an array of numbers. It has completely forgotten that there are two elements, and so the compiler complains that you are calling Math.atan2() with "0 or more" arguments when it wants exactly two.


Compare that to the code with as const:

const args = [8, 5] as const;
// const args: readonly [8, 5]
const angle = Math.atan2(...args); // okay
console.log(angle);

Now the compiler infers that args is of type readonly [8, 5]... a readonly tuple whose values are exactly the numbers 8 and 5 in that order. Specifically, args.length is known to be exactly 2 by the compiler.

And this is enough for the next line with Math.atan2() to work. The compiler knows that Math.atan2(...args) is the same as Math.atan2(8, 5), which is a valid call.


And again: at runtime, there is no difference whatsoever. Both versions log 1.0121970114513341 to the console. But const assertions, like the rest of the static type system, are not meant to have effects at runtime. Instead, they let the compiler know more about the intent of the code, and can more accurately tell the difference between correct code and bugs.

Playground link to code


* This isn't strictly true for array and tuple types; a readonly array or tuple is technically wider than a mutable version. A mutable array is considered a subtype of a readonly array; the former is not known to have mutation methods like push() while the latter does.

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • 1
    I have confused now about the below example. May I know why that example does not work? let b = (60 * 60 * 1000) as const; Actually, the document is mentioned that const assertions can only be applied immediately on simple literal expressions. But , the inside of the parenthesis looks okay. What I am missing? Thanks. – Cihat Şaman Apr 08 '21 at 18:33
  • 5
    @CihatŞaman - "'const' assertions can only be applied to references to enum members, or string, number, boolean, array, or object literals.". `60 * 60 * 1000` is not a literal, it is computed, see [the PR](https://github.com/Microsoft/TypeScript/pull/29510) introducing them for more details on the matter. There is an open issue on [adding math](https://github.com/microsoft/TypeScript/issues/26382) with literal types – Oleg Valter is with Ukraine Apr 08 '21 at 18:42
  • The two examples works without error in "Playground link to code", but says: "A spread argument must either have a tuple type or be passed to a rest parameter." – AliN11 Jun 26 '22 at 08:10
  • 2
    Wouldn't it be worth properly typing it vs using `as const`. Consider that `const args: Readonly<{ [key: string]: number}> = {...}` is better typed than `const args = {...} as const` and achieves the same security. – Levidps Sep 12 '22 at 05:59
  • 3
    I don't see how `{readonly [k: string]: number}` is "better typed" or "properly typed" compared to whatever comes out of the const assertion. I'd need to see what the `...` is before making a judgment. Perhaps you mean "explicitly typed" or "annotated" when you say "proper" or "better", but I don't know why explicit typing is to be preferred over inference. In short: ‍♂️ – jcalz Sep 12 '22 at 13:06
58

In short words, it lets you create fully readonly objects, this is known as const assertion, at your code as const means that the array positions values are readonly, here's an example of how it works:

const args = [8, 5] as const;
args[0] = 3;  // throws "Cannot assign to '0' because it is a read-only   
args.push(3); // throws "Property 'push' does not exist on type 'readonly [8, 5]'" 

You can see at the last thrown error, that args = [8, 5] as const is interpreted as args: readonly [8, 5], that's because the first declaration is equivalent to a readonly tuple.

Tuples have a lot of use cases, some examples are: spread parameters, destructive values, etc.. A general benefit is the readonly behaviour that is added to all its object attributes:

const args = [8, 5];

// Without `as const` assert; `args` stills a constant, but you can modify its attributes
args[0] = 3;  // -- WORKS ✓
args.push(3); // -- WORKS ✓

// You are only prevented from assigning values directly to your variable
args = 7;     // -- THROWS ERROR ✖

There's a few exceptions for the asserts being 'fully readonly', you can check them here. For more details, here's a list of other related question/answers that helped me understand the const assertion:

  1. How do I declare a read-only array tuple in TypeScript?
  2. Any difference between type assertions and the newer as operator in TypeScript?
  3. Typescript readonly tuples
luiscla27
  • 4,956
  • 37
  • 49
30

If you were to write const args = [8, 5], nothing would prevent you from then also writing args[0] = 23 or args.push(30) or anything else to modify that array. All you've done is tell TS/JS that the variable named args points to that specific array, so you can't change what it's referencing (e.g. you can't do args = "something else"). You can modify the array, you just can't change what its variable is pointing to.

On the other hand, adding as const to a declaration now really makes it constant. The whole thing is read-only, so you can't modify the array at all.


To clarify, as pointed out in the comments:

"really makes it constant" could imply that there is some runtime effect when there is none. At runtime, args.push(30) will still modify the array. All as const does is make it so that the TypeScript compiler will complain if it sees you doing it. – jcalz

as const only affects the compiler, and there is an exception to its read-only effect (see the comments). But in general, that's still the major use difference between const and as const. One is used to make a reference immutable and the other is used to make what's being referenced immutable.

user3781737
  • 932
  • 7
  • 17
  • 2
    Can't speak for whoever down-voted. Maybe they thought you ending statement was ambiguous (_"adding as const to a declaration now really makes it constant"_)? Not all declarations are completely readonly with `as const`: see [const assertions caveats](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#caveats-1). – Connor Low Apr 07 '21 at 20:22
  • 1
    Also no idea on the downvote, but "really makes it constant" could imply that there is some runtime effect when there is none. At runtime, `args.push(30)` will still modify the array. All `as const` does is make it so that the TypeScript compiler will complain if it sees you doing it. – jcalz Apr 07 '21 at 20:28
16

That is a const assertion. Here is a handy post on them, and here is the documentation.

When we construct new literal expressions with const assertions, we can signal to the language that

  • no literal types in that expression should be widened (e.g. no going from "hello" to string)
  • object literals get readonly properties
  • array literals become readonly tuples

With const args = [8, 5] as const;, the third bullet applies, and tsc will understand it to mean:

// Type: readonly [8, 5]
const args = [8, 5] as const;

// Ok
args[0];
args[1];

// Error: Tuple type 'readonly [8, 5]' of length '2' has no element at index '2'.
args[2];

Without the assertion:

// Type: number[]
const args = [8, 5];

// Ok
args[0];
args[1];

// Also Ok.
args[2];
Connor Low
  • 5,900
  • 3
  • 31
  • 52
16

as const when applied to an object or array it makes them immutable (i.e. making them read-only). For other literals it prevents type widening.

const args = [8, 5] as const;

args[0] = 10; ❌ Cannot assign to '0' because it is a read-only property.

Few other advantages :

  • You can catch bugs at compile-time without running the program if you don't cast to a different type.
  • The compiler will not allow you to reassign properties of nested objects.
Debug Diva
  • 26,058
  • 13
  • 70
  • 123
5

Amazing answers have been added already, just wanted to drop another as const example that might be useful to some, and is a bit different from the examples in other answers:

type Status = "success" | "danger"

function foo(status: Status){
  console.log({ status });
}

const status = "success";

// ❌ Error: Argument of type 'string' is not assignable to parameter of type 'Status'.
foo(status);

The reason it's not working is when typescript sees const status = "success", it infers the type of the status variable as a string, while the status parameter of the foo function is not of a type as broad as "string", it's very much more specific, it either has to be "success" or "danger".

To fix this you have two ways:

// #1: Call foo with "success" directly and TypeScript is smart enough to accept it:
foo("success");
// #2: Solution #1 however is not always useable, e.g., when status is received  from
// somewhere else, in that case, we have no other options but using a variable for it
// That's how it can be done using a variable:
foo("success" as const);

Actually, it's not different from what @jcalz said in their great answer:

A const assertion tells the compiler to infer the narrowest or most specific type it can for an expression.

aderchox
  • 3,163
  • 2
  • 28
  • 37