ZetCode

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.

widening.ts
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.

object_const.ts
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.

fetch_options.ts
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.

tuples.ts
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.

array_union.ts
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.

enum_alternative.ts
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.

deep_readonly.ts
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.

with_satisfies.ts
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.

limits.ts
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

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.