Exploring 'as const' in TypeScript: A Comprehensive Guide

TypeScript’s as const
annotation is a powerful yet straightforward tool that makes values deeply readonly. Unlike the typical as
keyword, often associated with type assertions (which can be misleading or unsafe), as const
is entirely type-safe. It simply provides TypeScript with additional information, ensuring your code is precise and immutable where needed.
Let’s break down as const
, understand how it differs from similar constructs, and see practical examples of its usage.
Key Takeaway:
as const
is a compile-time directive that provides immutability and precise type inference without runtime overhead.
What Does 'as const' Do?
When you append as const
to a value, you inform TypeScript to:
- Treat the value as deeply readonly. Every nested property becomes immutable.
- Infer literal types for the value’s contents wherever possible.
This makes as const
a compile-time directive that disappears at runtime, unlike Object.freeze
.
Example 1: Deep Readonly with as const
const settings = {
theme: {
color: "blue",
},
} as const;
// Error: Cannot assign to 'color' because it is a read-only property.
settings.theme.color = "red";
Contrast this with Object.freeze
:
const settings = Object.freeze({
theme: {
color: "blue",
},
});
// Surprisingly, this works because Object.freeze only affects the top level.
settings.theme.color = "red"; // No error at compile-time
How as const
Works with Arrays
Another key feature of as const
is how it transforms arrays into readonly tuples. This prevents operations that would alter the structure or content of the array.
const numbers = [10, 20, 30] as const;
// Error: Property 'push' does not exist on type 'readonly [10, 20, 30]'.
numbers.push(40);
// Error: Cannot assign to '0' because it is a read-only property.
numbers[0] = 50;
Without as const
, TypeScript infers numbers
as number[]
, allowing mutable operations like push
.
Example 2: Literal Type Inference
Using as const
ensures that TypeScript infers the most specific literal types rather than broader types.
const userInfo = {
role: "admin",
permissions: ["read", "write"],
};
// Inferred type:
// role: string
// permissions: string[]
console.log(userInfo.role);
const userInfoLiteral = {
role: "admin",
permissions: ["read", "write"],
} as const;
// Inferred type:
// role: "admin"
// permissions: readonly ["read", "write"]
console.log(userInfoLiteral.role);
This narrow inference is particularly useful for creating type-safe enums or constant values without the enum
keyword.
Example 3: Tuples in Return Values
When working with functions that return arrays, as const
can ensure the returned value is inferred as a tuple. This is useful in contexts like React’s state management.
declare function useToggle(): [boolean, () => void];
const useToggleState = () => {
const [isOn, toggle] = useToggle();
return [isOn, toggle] as const;
};
// TypeScript infers the return type as:
// readonly [boolean, () => void]
Comparing as const
with as
The standard as
keyword is often used to force TypeScript into accepting a specific type, which can lead to runtime errors if misused:
const element = {} as HTMLButtonElement;
// No error at compile-time, but will fail at runtime.
element.click();
In contrast, as const
doesn’t lie to TypeScript—it merely enhances the type inference system by locking down values and making them readonly.
Example 4: Function Props and as const
When defining object literals for function props, as const
ensures the types remain as narrow as possible.
const buttonProps = {
type: "submit" as const,
onClick: () => console.log("Submitted"),
} as const;
// Inferred:
// type: "submit"
// onClick: () => void
console.log(buttonProps.type);
Why Use as const
?
Advantages:
- Immutability: Prevents unintended mutations, leading to safer and more predictable code.
- Precise Inference: Helps TypeScript infer literal types and readonly structures.
- Zero Runtime Overhead: Unlike
Object.freeze
, it only affects the compile-time behavior.
Common Use Cases:
- Defining constant configurations or settings.
- Locking down API responses or constants in tests.
- Improving type inference for props and return values.
Key Takeaways
- Deep Readonly:
as const
makes entire objects or arrays immutable. - Literal Type Inference: It ensures values are inferred as narrowly as possible.
- Type-Safe: Unlike
as
, it doesn’t create runtime risks. - Disappears at Runtime: There’s no performance cost for using
as const
.
By using as const
, you can make your TypeScript code safer, more expressive, and easier to maintain—all without sacrificing runtime performance.