TypeScript as const
last modified October 15, 2026
When TypeScript infers the type of a value, it usually widens
literal values: "GET" becomes string and
[1, 2] becomes number[]. A const assertion, written
as as const after an expression, tells the compiler to infer the
narrowest possible type instead. Literal values keep their literal types,
arrays become readonly tuples, and object properties become readonly.
Literal types and widening
A const variable with a primitive value already has a
literal type. A let variable and properties of objects are
widened, because they can change.
const method = "GET"; // type "GET"
let mode = "dark"; // type string
const request = {
method: "GET", // type string
retries: 3, // type number
};
mode = "light";
request.method = "DELETE";
console.log(method, mode, request.method);
The comments show the inferred types. Because
request.method is a string, it can be changed to
any string, and the object cannot be passed where a "GET" |
"POST" value is expected.
$ npx tsx widening.ts GET light DELETE
Const assertion on an object
With as const, the properties keep their literal types
and become readonly.
const request = {
method: "GET",
retries: 3,
} as const;
request.method = "DELETE";
The type of request is
{ readonly method: "GET"; readonly retries: 3 }, so the
assignment is rejected. The assertion applies to nested objects and arrays as
well.
$ npx tsc --noEmit object_const.ts(6,9): error TS2540: Cannot assign to 'method' because it is a read-only property.
Passing literals to functions
A common reason for as const is passing an object to a
function that expects literal types.
type Method = "GET" | "POST" | "PUT" | "DELETE";
function send(url: string, options: { method: Method }): string {
return `${options.method} ${url}`;
}
const options = { method: "POST" } as const;
console.log(send("/api/users", options));
Without as const, options.method would be
a string and the call would not compile, because a
string is not assignable to Method.
$ npx tsx fetch_options.ts POST /api/users
Readonly tuples
An array literal with as const becomes a readonly
tuple with a fixed length and literal element types.
const point = [10, 20] as const;
const plain = [10, 20];
function distance(p: readonly [number, number]): number {
return Math.hypot(p[0], p[1]);
}
console.log(distance(point).toFixed(2));
console.log(point.length, plain.length);
The type of point is
readonly [10, 20], which can be passed as a pair of numbers. The
type of plain is number[], an array of any length,
which the function would not accept. Methods that modify the array, such as
push, are not available on a readonly tuple.
$ npx tsx tuples.ts 22.36 2 2
Union types from arrays
A const array can be the single source of both runtime values and
a union type. The indexed access type (typeof arr)[number]
produces the union of its elements.
const sizes = ["small", "medium", "large"] as const;
type Size = (typeof sizes)[number];
function isSize(value: string): value is Size {
return (sizes as readonly string[]).includes(value);
}
for (const input of ["medium", "huge"]) {
if (isSize(input)) {
console.log(`${input} is a valid size`);
} else {
console.log(`${input} is not one of: ${sizes.join(", ")}`);
}
}
The Size type is
"small" | "medium" | "large". Adding a new size to the array
updates the type automatically. The cast to readonly string[] is
needed because includes on the tuple only accepts its literal
types.
$ npx tsx array_union.ts medium is a valid size huge is not one of: small, medium, large
An alternative to enums
A const object with a derived union type is a popular alternative to enums. It is plain JavaScript, so it also works with tools that only strip types, such as Node.js.
const Direction = {
Up: "UP",
Down: "DOWN",
Left: "LEFT",
Right: "RIGHT",
} as const;
type Direction = (typeof Direction)[keyof typeof Direction];
function move(direction: Direction): string {
switch (direction) {
case Direction.Up:
return "moving up";
case Direction.Down:
return "moving down";
default:
return `moving ${direction.toLowerCase()}`;
}
}
console.log(move(Direction.Up));
console.log(move("LEFT"));
console.log(Object.values(Direction));
The value and the type share the name Direction. The
type is "UP" | "DOWN" | "LEFT" | "RIGHT". Unlike a string enum,
it also accepts the plain string "LEFT", which is convenient for
data coming from JSON.
$ npx tsx enum_alternative.ts moving up moving left [ 'UP', 'DOWN', 'LEFT', 'RIGHT' ]
Deeply readonly data
The assertion makes the whole structure readonly, including nested objects and arrays.
const config = {
server: { host: "localhost", port: 8080 },
features: ["search", "export"],
} as const;
config.server.port = 9090;
config.features.push("import");
Both modifications are rejected by the compiler. Note that
as const only affects types. At runtime, the object is an
ordinary mutable object; Object.freeze is needed to prevent
changes in JavaScript.
$ npx tsc --noEmit deep_readonly.ts(6,15): error TS2540: Cannot assign to 'port' because it is a read-only property. deep_readonly.ts(7,17): error TS2339: Property 'push' does not exist on type 'readonly ["search", "export"]'.
as const with satisfies
The satisfies operator checks a const object against a
type without losing its literal types.
type Theme = {
primary: string;
spacing: number;
};
const theme = {
primary: "#3366ff",
spacing: 8,
} as const satisfies Theme;
const padding = theme.spacing * 2;
console.log(theme.primary, padding);
The type of theme.spacing is 8, and the
object is guaranteed to have all properties of Theme. See the
satisfies operator tutorial for more
details.
$ npx tsx with_satisfies.ts #3366ff 16
Limits of const assertions
A const assertion can only be applied to literal expressions: strings, numbers, booleans, array and object literals, and enum members.
const values = [1, 2, 3]; const copy = values as const;
The values variable is not a literal, so the assertion
is rejected. It must be written directly after the literal.
$ npx tsc --noEmit limits.ts(3,14): error TS1355: A 'const' assertions can only be applied to references to enum members, or string, number, boolean, array, or object literals.
Best Practices
- Use as const for Fixed Data: Lists of options, lookup tables, and configuration objects are good candidates.
- Derive Types from Values: Use
(typeof arr)[number]andkeyof typeof objso that values and types cannot get out of sync. - Consider It Instead of Enums: A const object with a union type needs no special compilation and works with JSON data.
- Combine with satisfies: Use
as const satisfies Tto keep literal types and validate the shape. - Remember It Is Compile-Time Only: Use
Object.freezeif the data must also be immutable at runtime.
Source
TypeScript 3.4 release notes: const assertions
This tutorial covered const assertions in TypeScript. Use
as const for fixed data whose exact values matter to the type
system.
Author
List all TypeScript tutorials.