C.W.K.
Stream
Lesson 04 of 05 · published

`as const`: Freezing the Literal World

~10 min · arrays-tuples, as-const, literal-types, readonly

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"as const is three features in one keyword: readonly, narrow, tuple. Use it whenever you want the value to be exactly itself."

What as const does

The as const assertion does three things simultaneously to whatever it's applied to:

  1. Every value is narrowed from its widened type to its literal type. 'red' instead of string. 42 instead of number. true instead of boolean.
  2. Every property becomes readonly. Object properties get a readonly modifier; arrays become readonly arrays.
  3. Array literals become tuples (fixed-length, with per-position types) instead of arrays (variable length, with a single element type).

The combined effect: the value's type becomes exactly its shape, with no widening, no mutation, no flexibility. It's the strongest form of "I really mean this exact thing."

When to reach for it

1. Fixed configuration objects. const ROUTES = { CHAT: '/chat', LOGIN: '/login' } as const — every value becomes its literal string type, and the object can't be mutated. If you later type a function parameter as route: typeof ROUTES[keyof typeof ROUTES], you get a literal union of the actual route strings.

2. Fixed lists of constants. const COLORS = ['red', 'green', 'blue'] as const — the array is readonly ['red', 'green', 'blue'], a tuple of literal types. typeof COLORS[number] gives you the literal union 'red' | 'green' | 'blue'.

3. Object literals passed to functions with literal-union parameters. Without as const, { method: 'POST' } infers { method: string }, which won't satisfy a parameter typed { method: 'GET' | 'POST' }. Adding as const makes the property a literal type, and the call type-checks.

The slogan: if you want the compiler to remember exactly what value you wrote, as const is the way to say so. If widening is fine (because the value is going to mutate or be reassigned), leave it off.

The combined-with-typeof pattern

as const shines when you derive a type from the value. const ROLES = ['admin', 'editor', 'viewer'] as const gives you a readonly tuple. Then type Role = typeof ROLES[number] gives you the literal union 'admin' | 'editor' | 'viewer'. You now have a single source of truth that's both runtime-iterable and type-system-known. This pattern shows up everywhere in TypeScript-aware codebases.

Pippa's confession

cwkPippa's frontend uses the as const + typeof X[number] pattern for the brain list, the council mode list, the reasoning levels, and several other small enums-by-convention. The runtime gets a plain array; the type system gets a literal union; both come from one declaration. The pattern is so useful that I'd recommend learning it on day one.

Code

Three effects, one assertion·typescript
// Three effects in one keyword.

// 1. Narrow values to their literal type.
const a = 'red';                     // a: string
const b = 'red' as const;             // b: 'red'

// 2. Freeze properties.
const c = { color: 'red' };          // c: { color: string } — mutable
const d = { color: 'red' } as const; // d: { readonly color: 'red' }
d.color = 'blue';                    // ❌

// 3. Arrays become tuples.
const e = ['a', 'b'];                // e: string[]
const f = ['a', 'b'] as const;       // f: readonly ['a', 'b']
f.push('c');                         // ❌ — and 'c' wouldn't be valid anyway
as const + typeof[number] — the pattern worth memorizing·typescript
// The headline pattern — single source of truth.

const BRAINS = ['claude', 'codex', 'gemini', 'ollama'] as const;
// BRAINS: readonly ['claude', 'codex', 'gemini', 'ollama']

type Brain = typeof BRAINS[number];
// Brain: 'claude' | 'codex' | 'gemini' | 'ollama'

// Runtime use:
for (const b of BRAINS) {
  console.log(b);   // each iteration is one of the literal types
}

// Type-system use:
function selectBrain(name: Brain) { /* ... */ }
selectBrain('claude');   // ✅
selectBrain('typo');     // ❌ — literal-type checking from a runtime array

External links

Exercise

Declare a fixed list of status codes: const STATUSES = ['idle', 'loading', 'done', 'error']. Then declare a function format(status: ???) that accepts only those four values. Now add as const to the declaration and derive the type via typeof STATUSES[number]. Notice how you went from a string array to a typed enum without writing an enum or a literal union by hand.
Hint
Without as const, STATUSES[0] is just string. With as const, it's 'idle'. The typeof X[number] trick unions all positions of the tuple into a single literal-union type — automatic and refactor-safe.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.