Const Assertions in TypeScript
January 06, 2025 | JavaScript / TypeScript
TypeScript’s const
assertion (as const
) is a powerful feature that allows you to create more precise type definitions. It ensures that your values are treated as immutable and infers literal types instead of broader ones.
What is a Const Assertion?
A const
assertion is a TypeScript feature that tells the compiler to infer the most specific, immutable type possible for a value. When you use as const
, TypeScript treats the value as:
- Read-only: The value cannot be reassigned.
- Literal type: Instead of general types like
string
,number
, orarray
, the value is inferred as a literal type.
Example Without as const
const colors = ["red", "blue", "green"];
// TypeScript infers the type as:
// string[]
colors.push("yellow"); // Valid, as arrays are mutable
Example With as const
const colors = ["red", "blue", "green"] as const;
// TypeScript infers the type as:
// readonly ["red", "blue", "green"]
colors.push("yellow"); // Error: Property 'push' does not exist on type 'readonly ["red", "blue", "green"]'
How Does as const
Work?
When you use as const
, TypeScript:
- Converts arrays into immutable tuples.
- Narrows string, number, or boolean values to their literal types.
- Marks the object or array as
readonly
, making it immutable.
Example with Objects
const config = {
apiKey: "12345",
retries: 3
} as const;
// TypeScript infers the type as:
// {
// readonly apiKey: "12345";
// readonly retries: 3;
// }
config.apiKey = "67890"; // Error: Cannot assign to 'apiKey' because it is a read-only property
When to Use Const Assertions
1. Enforcing Immutability
If you want to ensure that a value cannot be modified at runtime, as const
helps enforce immutability:
const roles = ["admin", "editor", "viewer"] as const;
roles[0] = "user"; // Error: Index signature in type 'readonly ["admin", "editor", "viewer"]' only permits reading
2. Narrowing Types for Function Parameters
Using as const
helps TypeScript infer the most specific type, which can improve type checking in functions:
function setRole(role: "admin" | "editor" | "viewer") {
console.log(role);
}
const userRole = "admin" as const;
setRole(userRole); // Valid
Without as const
, userRole
would be inferred as string
, causing a type mismatch.
3. Working with Literal Types
If you’re working with unions or discriminated unions, as const
ensures precise type inference:
type ButtonStyle = "primary" | "secondary" | "tertiary";
const buttonStyles = ["primary", "secondary", "tertiary"] as const;
type ButtonStyleType = typeof buttonStyles[number];
// ButtonStyleType is "primary" | "secondary" | "tertiary"
4. Creating Immutable Data Structures
If you want to create data structures that should never change, as const
is a great choice:
const errorCodes = {
NotFound: 404,
Unauthorized: 401,
} as const;
// TypeScript infers:
// {
// readonly NotFound: 404;
// readonly Unauthorized: 401;
// }
function handleError(code: typeof errorCodes[keyof typeof errorCodes]) {
console.error(code);
}
handleError(404); // Valid
handleError(500); // Error: Argument of type '500' is not assignable
Limitations of Const Assertions
- Only for Literals: You can’t use
as const
with variables that are already mutable. - Deep Immutability:
as const
only applies to the first level. Nested objects or arrays remain mutable unless explicitly marked asreadonly
.
Example:
const nested = {
outer: {
inner: "value"
}
} as const;
nested.outer.inner = "newValue"; // Valid, as 'inner' is not readonly
To achieve deep immutability, consider using libraries like immer
or deep-freeze
.