Question
I am confused about the as const assertion in TypeScript. I have looked at documentation and videos, but I still do not fully understand it.
What does as const mean in the code below, and what is the benefit of using it?
const args = [8, 5] as const;
const angle = Math.atan2(...args);
console.log(angle);
Short Answer
By the end of this page, you will understand what as const does in TypeScript, how it changes literal inference, why arrays become readonly tuples, and when it is useful in everyday code. You will also see how it helps functions like Math.atan2(...args) type-check correctly.
Concept
as const is a const assertion in TypeScript. It tells TypeScript to infer the most specific, narrowest possible type for a value instead of a wider, more general one.
In practice, as const does three important things:
- It keeps literal values as literal types.
- It makes object properties
readonly. - It turns array literals into
readonlytuples.
Consider this example:
const args = [8, 5];
Without as const, TypeScript usually infers:
number[]
That means: “this is an array of numbers of any length.”
But with:
const args = [8, 5] as const;
TypeScript infers:
readonly [8, 5]
That means: “this is a tuple with exactly two items, where the first is 8 and the second is 5, and the tuple cannot be mutated.”
This matters because some functions expect a fixed number of arguments. Math.atan2 takes exactly two arguments:
Math.atan2(y, x)
When you write:
Math.atan2(...args)
TypeScript needs to know that args contains exactly two values. A plain number[] does not guarantee that. A tuple does.
So the benefit of as const here is that it preserves the array as a fixed two-value tuple, allowing the spread call to be type-safe.
This feature matters in real programming because it helps TypeScript catch mistakes earlier. It is especially useful with:
- configuration objects
- action type constants
- fixed option lists
- function arguments stored in arrays
- discriminated unions
- lookup objects
In short, as const makes your values more precise, which makes your types safer.
Mental Model
Think of TypeScript as trying to label boxes.
Without as const, if it sees this:
[8, 5]
it puts a broad label on the box:
- “This box contains numbers.”
It does not promise how many numbers there are.
With as const, TypeScript puts a much more exact label on the box:
- “This box contains exactly two items: first
8, then5, and nobody should change them.”
That extra precision is why spreading the array into a function works better. The function can trust the shape of the data.
So as const is like telling TypeScript:
- “Do not generalize this value.”
- “Keep it exactly as written.”
Syntax and Examples
The basic syntax is:
const value = expression as const;
Example 1: Literal values stay narrow
const status1 = "ok";
const status2 = "ok" as const;
status1 is often inferred as:
"ok"
But as const is especially useful in objects and arrays, where TypeScript would otherwise widen values more often.
Example 2: Object properties become readonly
const config = {
mode: "dark",
fontSize: 16,
} as const;
Type:
{
readonly mode: "dark";
readonly fontSize: 16;
}
This means:
modeis exactly"dark", not juststringfontSizeis exactly16, not justnumber- properties cannot be reassigned
// Error
config.mode = "light";
Example 3: Arrays become readonly tuples
const point = [10, 20] as const;
Type:
readonly [10, 20]
This is different from:
const point = [10, 20];
Type:
number[]
Example 4: Why it helps with function arguments
const args = [8, 5] as const;
const angle = Math.atan2(...args);
Because args is a tuple of exactly two items, TypeScript knows it is safe to spread into Math.atan2(y, x).
Without as const:
const args = [8, 5];
const angle = Math.atan2(...args);
TypeScript may complain because args is just number[], and a number[] could have 0, 1, 2, or many values.
Example 5: Useful for fixed lists
const roles = ["admin", "editor", "viewer"] as const;
Now each item is a specific string literal, which is useful for deriving union types:
type Role = typeof roles[number];
// "admin" | "editor" | "viewer"
Step by Step Execution
Consider this code:
const args = [8, 5] as const;
const angle = Math.atan2(...args);
console.log(angle);
Step 1: Create the array literal
[8, 5]
This is an array literal with two numbers.
Step 2: Apply as const
const args = [8, 5] as const;
TypeScript infers the type as:
readonly [8, 5]
That means:
- first item is
8 - second item is
5 - there are exactly two items
- the tuple is readonly
Step 3: Spread the tuple into the function call
Math.atan2(...args)
This becomes equivalent to:
Math.atan2(8, 5)
TypeScript accepts this because the tuple guarantees two elements.
Step 4: Compute the angle
Math.atan2(y, x) returns the angle in radians from the positive x-axis to the point (x, y).
So here:
y = 8x = 5
Step 5: Print the result
console.log(angle);
The output is a number in radians.
Compare with no as const
const args = [8, 5];
const angle = Math.atan2(...args);
Now args is typed as:
number[]
A number[] does not guarantee exactly two elements, so TypeScript cannot safely assume it matches the function signature.
Real World Use Cases
as const is useful whenever a value should be treated as fixed and precise.
1. API request methods
const METHODS = ["GET", "POST", "PUT", "DELETE"] as const;
type HttpMethod = typeof METHODS[number];
This creates a safe union of allowed method names.
2. Redux-style action objects
const action = {
type: "INCREMENT",
} as const;
Now type is exactly "INCREMENT", which helps with action type checking.
3. Configuration objects
const settings = {
theme: "dark",
layout: "grid",
} as const;
Useful when settings should not be changed accidentally.
4. Route definitions
const routes = ["/", "/login", "/dashboard"] as const;
You can derive a union of valid route strings.
5. Fixed coordinate or argument lists
const rgb = [255, 120, 0] as const;
This is useful when a function expects a fixed number of values.
6. Status values
const statuses = ["idle", "loading", "success", "error"] as const;
type Status = typeof statuses[number];
This helps avoid typos like "sucess".
Real Codebase Usage
In real projects, developers use as const to improve type safety without writing extra type definitions.
Common patterns
Guarding fixed values
const VALID_STATUSES = ["pending", "paid", "failed"] as const;
type Status = typeof VALID_STATUSES[number];
This keeps allowed values in one place.
Safer configuration
const APP_CONFIG = {
env: "production",
retries: 3,
} as const;
This prevents accidental mutation and preserves precise values.
Action creators
const setCount = (count: number) => ({
type: "SET_COUNT",
payload: count,
} as const);
This helps TypeScript distinguish one action type from another.
Mapping names to constant values
const COLORS = {
primary: "#3366ff",
danger: "#ff3333",
} as const;
This gives readonly properties and exact string values.
Fixed argument tuples
const pair = [10, 20] as const;
someFunction(...pair);
This is especially common when passing prebuilt arguments into a function.
Why teams like it
- less manual type writing
- better autocomplete
- fewer invalid values
- safer refactoring
- improved support for union types and discriminated unions
Common Mistakes
1. Thinking as const is the same as const
These are different.
const name = "Sam";
const means the variable cannot be reassigned.
const user = { name: "Sam" };
You still can do this:
user.name = "Alex";
But with as const:
const user = { name: "Sam" } as const;
Now this is an error:
user.name = "Alex";
2. Using as const when you actually need mutability
Broken idea:
const numbers = [1, 2, 3] as const;
numbers.push(4); // Error
If you plan to change the array, do not use as const.
3. Assuming it affects runtime behavior
as const is only for TypeScript's type system. It does not freeze the value at runtime.
const obj = { x: 1 } as const;
This adds compile-time readonly typing, not JavaScript runtime immutability like Object.freeze().
4. Overusing it everywhere
Not every value needs maximum precision. Use it when exact literals or readonly tuples are actually helpful.
5. Confusing tuples with arrays
const values = [1, 2] as const;
This is not a normal mutable number[]. It is a readonly tuple.
6. Expecting spread calls to work with normal arrays
Broken example:
const args = [8, 5];
Math.atan2(...args); // May produce a type error
Fix:
const args = [8, 5] as const;
Math.atan2(...args);
Or explicitly type the tuple:
const args: [number, number] = [8, 5];
Math.atan2(...args);
Comparisons
| Concept | What it does | Mutability | Example inferred type |
|---|---|---|---|
const x = ... | Prevents variable reassignment | Value may still be mutable | const arr = [1, 2] -> number[] |
as const | Preserves exact literal types | Readonly at type level | readonly [1, 2] |
| Explicit type annotation | You manually choose the type | Depends on the type | const arr: [number, number] = [1, 2] |
const vs as const
const arr1 = [8, 5];
// type: number[]
const arr2 = [8, 5] as const;
// type: readonly [8, 5]
const only stops reassignment of arr1 or arr2.
as const changes how TypeScript infers the inner value.
as const vs explicit tuple type
const a = [8, 5] as const;
// readonly [8, 5]
const b: [number, number] = [8, 5];
// [number, number]
Use as const when you want the exact literal values and readonly behavior.
Use an explicit tuple type when you want fixed length but not exact literal values.
as const vs Object.freeze()
const obj = { mode: "dark" } as const;
const frozen = Object.freeze({ mode: "dark" });
as constis a TypeScript type assertionObject.freeze()is a JavaScript runtime operation
They solve related but different problems.
Cheat Sheet
Quick rules
as consttells TypeScript to keep a value as specific as possible.- String, number, and boolean literals stay literal.
- Object properties become
readonly. - Arrays become
readonlytuples.
Syntax
const value = expr as const;
Common examples
const status = "ok" as const;
// type: "ok"
const point = [10, 20] as const;
// type: readonly [10, 20]
const config = { theme: "dark" } as const;
// type: { readonly theme: "dark" }
Good use cases
- fixed lists of allowed values
- action type objects
- function argument tuples
- config objects that should not change
- deriving union types from arrays
Important note
const arr = [1, 2] as const;
This means:
- exact values
- exact length
- readonly tuple
It does not mean runtime freezing.
Alternative
If you want a fixed-length tuple without literal values:
const args: [number, number] = [8, 5];
FAQ
What does as const do in TypeScript?
It tells TypeScript to infer the narrowest possible type for a value, making literals exact, object properties readonly, and arrays readonly tuples.
Why is as const useful with arrays?
It turns an array literal into a readonly tuple, which is helpful when a function expects a fixed number of arguments.
Why does Math.atan2(...args) work better with as const?
Because Math.atan2 expects exactly two arguments, and as const tells TypeScript that args contains exactly two items.
Is as const the same as const?
No. const prevents variable reassignment. as const changes type inference to be more specific.
Does as const freeze objects at runtime?
No. It only affects TypeScript's compile-time type checking. It does not make the object truly immutable in JavaScript.
When should I avoid as const?
Avoid it when you need to mutate the object or array later, because it makes properties and tuple elements readonly.
Can I use an explicit type instead of as const?
Yes. For example, const args: [number, number] = [8, 5]; also works for fixed-length tuples.
Is as const only for arrays?
No. It also works with strings, numbers, booleans, and object literals.
Mini Project
Description
Build a small TypeScript utility for application statuses. The project demonstrates how as const helps define a fixed list of allowed values and safely validate user input against that list.
Goal
Create a type-safe status system using as const, then validate and print statuses without allowing invalid values.
Requirements
- Create a constant array of allowed statuses using
as const. - Derive a
Statustype from that array. - Write a function that checks whether a string is a valid status.
- Write a function that prints a message for a valid status.
- Test the code with both valid and invalid input.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.