const in JavaScript prevents reassigning the variable binding, but it does not make objects or arrays immutable. TypeScript’s as const assertion (Const Assertions) bridges this gap by locking down values into deeply immutable, narrow literal types at compile time.1. The Shallow Nature of JavaScript const#
In JavaScript, const only guarantees that a variable reference cannot be reassigned. The internal contents of an object or array remain completely mutable:
// Valid JavaScript! 'const' does NOT stop mutation:
const user = { role: "admin" };
user.role = "superadmin"; // Allowed!
const tags = ["js", "ts"];
tags.push("python"); // Allowed!
Because JavaScript objects are mutable, TypeScript defaults to Literal Widening for object properties (as covered in Episode 8), inferring user.role as string rather than "admin".
2. The 3 Transformation Rules of as const#
When you append as const to an expression, TypeScript applies 3 strict transformations:
- No Literal Widening: Primitive literal values are NOT widened (
"GET"stays"GET", notstring). - Deep
readonlyObjects: Object properties recursively receivereadonlymodifiers. - Readonly Tuples: Array literals are converted into
readonlyfixed-length Tuples.
// Without 'as const'
const standardConfig = {
endpoint: "https://api.acme.com",
timeout: 5000,
retries: [1, 2, 3],
};
/* Inferred Type:
{
endpoint: string;
timeout: number;
retries: number[];
}
*/
// With 'as const' (Const Assertion)
const immutableConfig = {
endpoint: "https://api.acme.com",
timeout: 5000,
retries: [1, 2, 3],
} as const;
/* Inferred Type:
{
readonly endpoint: "https://api.acme.com";
readonly timeout: 5000;
readonly retries: readonly [1, 2, 3]; // Readonly Tuple!
}
*/Attempting Mutations on as const Objects#
// ❌ Compiler Error: Cannot assign to 'endpoint' because it is a read-only property.
// immutableConfig.endpoint = "https://other-api.com";
// ❌ Compiler Error: Property 'push' does not exist on type 'readonly [1, 2, 3]'.
// immutableConfig.retries.push(4);
3. as const vs Object.freeze()#
It is vital to understand the difference between compile-time const assertions and runtime object freezing:
| Feature | as const | Object.freeze() |
|---|---|---|
| Enforcement Layer | Compile-Time (TypeScript) | Runtime (JavaScript V8 Engine) |
| JS Code Footprint | Erased during compilation (0 bytes) | Remains in compiled JS bundle |
| Depth | Deep (Recursively freezes nested objects) | Shallow (Only freezes top-level keys) |
| Array Transformation | Converts T[] to readonly [T1, T2] | Freezes array instance methods |
// Combining both for ultimate runtime AND compile-time immutability:
const productionSettings = Object.freeze({
env: "production",
port: 8080,
} as const);4. Deriving Union Types from as const Arrays (typeof ARRAY[number])#
One of the most powerful architectural patterns in TypeScript is creating a single source of truth for runtime values, and deriving compile-time TypeScript Union Types directly from it.
The Single-Source-of-Truth Pattern#
Instead of maintaining a separate type Role = "admin" | "user" | "guest" and a separate runtime array const ROLES = ["admin", "user", "guest"], define the array once with as const:
// 1. Define the single source of truth runtime array with 'as const'
export const SUPPORTED_ROLES = ["admin", "editor", "viewer"] as const;
// 2. Derive the TypeScript Union Type using Indexed Access:
// Inferred Type: "admin" | "editor" | "viewer"
export type Role = typeof SUPPORTED_ROLES[number];
// 3. Use the derived type in application functions!
function assignRole(user: string, role: Role) {
console.log(`Assigned role ${role} to ${user}`);
}
assignRole("Alice", "admin"); // 🟢 Valid!
// ❌ Compiler Error: Argument of type '"superadmin"' is not assignable to parameter of type 'Role'.
// assignRole("Bob", "superadmin");
How typeof SUPPORTED_ROLES[number] Works#
typeof SUPPORTED_ROLESretrieves the tuple type:readonly ["admin", "editor", "viewer"].[number]indexes into the tuple using any number index, extracting the union of all element types:"admin" | "editor" | "viewer".
5. Deriving Union Types from as const Objects (typeof OBJECT[keyof typeof OBJECT])#
You can also derive a union of values from an object dictionary:
export const LOG_LEVELS = {
DEBUG: 10,
INFO: 20,
WARN: 30,
ERROR: 40,
} as const;
// Derive Union of Object Keys: "DEBUG" | "INFO" | "WARN" | "ERROR"
export type LogLevelKey = keyof typeof LOG_LEVELS;
// Derive Union of Object Values: 10 | 20 | 30 | 40
export type LogLevelValue = typeof LOG_LEVELS[keyof typeof LOG_LEVELS];
function setThreshold(level: LogLevelValue) {
console.log(`Setting log threshold to level number: ${level}`);
}
setThreshold(20); // Valid (INFO)
// setThreshold(99); // ❌ Error! 99 is not a valid LogLevelValue.
Module 1 Completion Milestone! 🎉#
Congratulations! You have completed Module 1: Fundamental TypeScript Essentials!
Throughout these 15 episodes, you have mastered:
- The
tsccompiler pipeline, AST parsing, and stricttsconfig.jsonflags. - Primitive types, wrapper object traps, and
strictNullChecks. - Literal Widening, Contextual Typing, and Type Annotations vs Inference.
- Structural Typing, Excess Property Checks, and Index Signatures.
- Arrays, Tuples, Labeled Tuples, and
readonlycollections. anyvsunknown, Top Types, anderror: unknowncatch blocks.- Set Theory mechanics of Unions (
|) and Intersections (&). - Unit Literal Types and String Literal Unions.
- Control Flow Analysis,
typeof,instanceof, andinguards. - Custom Type Guards (
arg is Type) and Assertion Functions. - Discriminated Unions (Sum Types) and eliminating impossible states.
- Function Annotations, Call Signatures, and
voidreturn semantics. - Function Overloads and signature order.
- The
neverBottom Type ($\bot$) and Exhaustiveness Checking. as constassertions and single-source-of-truth type derivation.
In the next module, Module 2: Object-Oriented TypeScript (Classes & OOP), we will explore TypeScript’s class extensions, access modifiers (public, private, protected), parameter properties, abstract classes, and the polymorphic this type!

