← Concepts

'as const' Annotations

Quick Explanation

  • as const can be used to mark a value as deeply readonly - i.e., it can't be mutated in any way.

  • You might be used to as meaning some kind of type assertion - a lie to TypeScript. Confusingly, this use of as is completely type-safe. You're simply giving TypeScript more information about your code.

  • It's different from Object.freeze in two ways. First, it only runs at compile-time, so it disappears at runtime. Second, Object.freeze only works on the top level. as const works on the entire object.

ts
const obj = {
foo: {
bar: 42,
},
} as const;
 
// Error!
obj.foo.bar = 43;
Cannot assign to 'bar' because it is a read-only property.2540Cannot assign to 'bar' because it is a read-only property.
 
const freezedObj = Object.freeze({
foo: {
bar: 42,
},
});
 
// Works!
freezedObj.foo.bar = 43;
  • It's useful on objects, but can also be used on arrays to turn them into readonly tuples:
ts
const arr = [1, 2, 3] as const;
 
arr.push(4);
Property 'push' does not exist on type 'readonly [1, 2, 3]'.2339Property 'push' does not exist on type 'readonly [1, 2, 3]'.
  • Objects and arrays marked with as const get inferred as their literal types, not the wider types. This can be useful for creating type-safe enums without needing the enum keyword.
ts
const obj = {
foo: {
bar: 42,
},
};
 
// Inferred as number
console.log(obj.foo.bar);
(property) bar: number
 
const obj2 = {
foo: {
bar: 42,
},
} as const;
 
// Inferred as its literal!
console.log(obj2.foo.bar);
(property) bar: 42
  • as const can also be used to force values to infer as narrowly as possible - for instance, inside objects:
ts
const buttonProps = {
type: "button" as const,
onClick: () => {
console.log("clicked");
},
} as const;
 
// Inferred as "button", not string
console.log(buttonProps.type);
(property) type: "button"
  • It can also be used to encourage a function that returns an array to infer as a tuple:
ts
declare const useState: () => [
string,
(newState: string) => void
];
 
const useStateWrapped = () => {
const [state, setState] = useState();
 
return [state, setState] as const;
};

Isn't as Bad?

as is often used to lie to TypeScript. For instance, you can lie that an empty object is a DOM node, and call functions on it:

ts
const node = {} as HTMLAudioElement;
 
node.play();

TypeScript won't complain, but this will throw an error at runtime.

But as const is different. It's not a lie - it's just giving TypeScript more information about your code. It's like a type annotation, but for values instead of types.

Share this TypeScript Concept with your friends