What Does 'as const' Mean in TypeScript? Use Cases and Examples
TypeScript as const is a type assertion that tells the compiler to infer the narrowest possible type for a value. Instead of widening "hello" to string or [1, 2] to number[], TypeScript treats every property as its literal type, marks arrays as readonly tuples, and makes all object properties readonly. It produces zero runtime JavaScript. It exists purely in the type system to give you maximal type safety without manual type annotations.
// Without as const, TypeScript widens to string.
const direction = "north";
// Type: "north" (already narrow because of const binding)
let directionLet = "north";
// Type: string (widened because let is reassignable)
let directionNarrow = "north" as const;
// Type: "north" (literal type preserved despite let binding)
Where as const Actually Matters: Objects and Arrays
A const variable binding only prevents reassignment of the variable itself. It does nothing about the types of properties inside an object or elements inside an array, because those still get widened. This is where as const shines. It recursively narrows every nested value to its literal type and marks the entire structure as deeply readonly.
const config = {
endpoint: "https://api.example.com",
retries: 3,
methods: ["GET", "POST"],
};
// Type: { endpoint: string; retries: number; methods: string[] }
const configNarrow = {
endpoint: "https://api.example.com",
retries: 3,
methods: ["GET", "POST"],
} as const;
// Type: {
// readonly endpoint: "https://api.example.com";
// readonly retries: 3;
// readonly methods: readonly ["GET", "POST"];
// }
Use Case 1: Type-Safe Lookup Maps Instead of Enums
as const objects are the idiomatic TypeScript replacement for enums. They produce real JavaScript objects that you can iterate over at runtime, and you can derive union types from them with typeof. Enums have well-known quirks: numeric enums allow reverse mapping bugs, and some build tools like esbuild removed support for const enum. A plain object with as const avoids all of that.
const Status = {
Idle: "IDLE",
Loading: "LOADING",
Success: "SUCCESS",
Error: "ERROR",
} as const;
// Derive the union type from the object values.
type StatusValue = (typeof Status)[keyof typeof Status];
// Type: "IDLE" | "LOADING" | "SUCCESS" | "ERROR"
function handleStatus(status: StatusValue) {
// TypeScript narrows status to exact literal strings.
if (status === Status.Error) {
console.error("Request failed");
}
}
Use Case 2: Readonly Tuples for Function Parameters
Without as const, TypeScript types an array literal as a mutable array with widened element types. This breaks when you pass it to a function that expects a tuple or a union of specific literals. Applying as const locks the array into a readonly tuple with exact literal types, which satisfies stricter function signatures.
function navigate(point: readonly [number, number, number]) {
const [x, y, z] = point;
console.log(`Moving to ${x}, ${y}, ${z}`);
}
// Without as const: type is number[] — fails.
// navigate([10, 20, 30]);
// With as const: type is readonly [10, 20, 30] — passes.
navigate([10, 20, 30] as const);
Use Case 3: Discriminated Unions from Configuration
Discriminated unions are TypeScript's most powerful pattern matching tool. as const lets you define action creators or event definitions as plain objects while keeping the discriminant property's literal type intact. This enables exhaustive switch statements.
function createAction(
type: T,
payload: P
) {
return { type, payload } as const;
}
// Type: { readonly type: "INCREMENT"; readonly payload: number }
const increment = createAction("INCREMENT", 5);
// Type: { readonly type: "RESET"; readonly payload: undefined }
const reset = createAction("RESET", undefined);
type AppAction = typeof increment | typeof reset;
function reducer(action: AppAction) {
switch (action.type) {
case "INCREMENT": return action.payload + 1;
case "RESET": return 0;
}
}
The as const satisfies Combo (TypeScript 4.9+)
A common pitfall with as const alone is that you lose validation against a known shape. If you misspell a key or add the wrong value type, TypeScript won't warn you. It infers whatever you wrote. Combining satisfies with as const gives you the best of both worlds: the compiler checks that your object conforms to a type and you keep the narrowed literal types.
type RouteMap = Record;
// Validates structure AND preserves literal types.
const routes = {
home: { path: "/", auth: false },
dashboard: { path: "/dashboard", auth: true },
// Typo like "auht: true" would be caught here.
} as const satisfies RouteMap;
// Type is the narrow literal, not just string.
type DashboardPath = (typeof routes)["dashboard"]["path"];
// Type: "/dashboard"
Gotchas and Pitfalls
as const makes everything deeply readonly. If you pass an as const array to a function that expects a mutable string[], TypeScript throws an error. The fix is to update the function signature to accept readonly string[]. This is the correct approach anyway, because most functions don't mutate their inputs.
as const works only on literal expressions. You can't apply it to a variable reference or a function return value at the call site. It must appear directly after an object, array, or primitive literal. If you need a function to return a narrow type, use as const inside the function's return statement or use a generic parameter to capture the literal.
Don't confuse as const with Object.freeze. as const is erased at compile time and provides no runtime immutability. Object.freeze prevents mutation at runtime but applies only shallowly, and its TypeScript typing is weaker. Use both together if you need compile-time and runtime safety.
// Gotcha: readonly array vs mutable array.
const ids = [1, 2, 3] as const;
function processIds(input: number[]) {
input.push(4);
}
// Error: readonly [1, 2, 3] is not assignable to number[].
// processIds(ids);
// Fix: accept readonly array since you should not mutate input.
function processIdsFixed(input: readonly number[]) {
return input.map((id) => id * 2);
}
processIdsFixed(ids);
When to Use as const vs. Explicit Types
Use as const when the value defines the type: lookup maps, configuration objects, route definitions, action types, and string literal unions derived from runtime data. Use explicit type annotations when the type defines the contract: function parameters, class properties, and API response shapes. Combining both via as const satisfies SomeType is the sweet spot when you need validation and narrow inference at the same time. In TypeScript 4.9+ projects, reach for this combo by default. There's almost no reason not to.