TypeScript Readonly & Const Assertions
Create immutable values and types
🔒 What are Readonly & Const Assertions?
Readonly and const assertions make values immutable in TypeScript. Readonly prevents property modifications, while const assertions (as const) create the most specific literal types possible, making values deeply immutable.
const config = { api: "https://api.com", timeout: 5000 } as const;
// config.timeout = 3000; // ✗ Error: Cannot assign to readonly
Key Concepts
readonly Modifier
Prevent property reassignment
type User = {
readonly id: number;
name: string;
};
as const
Create literal types
const colors = ["red", "blue"] as const;
// Type: readonly ["red", "blue"]
Deep Immutability
Make nested objects readonly
const config = {
db: { host: "localhost" }
} as const;
Literal Types
Narrow types to exact values
const status = "active" as const;
// Type: "active" (not string)
🔹 readonly Property Modifier
Prevent property modifications in types:
type User = {
readonly id: number;
name: string;
readonly email: string;
};
const user: User = {
id: 1,
name: "Alice",
email: "[email protected]"
};
user.name = "Bob"; // ✓ Valid: name is not readonly
// user.id = 2; // ✗ Error: Cannot assign to readonly property
// user.email = "new"; // ✗ Error: Cannot assign to readonly property
console.log(user.id); // ✓ Can read readonly properties
🔹 Readonly Arrays
Prevent array modifications:
// Using ReadonlyArray type
const numbers: ReadonlyArray<number> = [1, 2, 3, 4, 5];
console.log(numbers[0]); // ✓ Can read
// numbers[0] = 10; // ✗ Error: Index signature is readonly
// numbers.push(6); // ✗ Error: push doesn't exist on readonly array
// numbers.pop(); // ✗ Error: pop doesn't exist
// Using readonly modifier (shorter syntax)
const colors: readonly string[] = ["red", "green", "blue"];
console.log(colors[1]); // ✓ Can read
// colors[1] = "yellow"; // ✗ Error: Cannot assign
// colors.push("purple"); // ✗ Error: push doesn't exist
🔹 Const Assertions Basics
Create literal types with as const:
// Without as const
const name1 = "Alice"; // Type: string
const age1 = 30; // Type: number
const active1 = true; // Type: boolean
// With as const
const name2 = "Alice" as const; // Type: "Alice"
const age2 = 30 as const; // Type: 30
const active2 = true as const; // Type: true
// For objects
const user = {
name: "Bob",
age: 25
} as const;
// Type: { readonly name: "Bob"; readonly age: 25 }
// user.name = "Charlie"; // ✗ Error: Cannot assign to readonly
🔹 Const Assertions with Arrays
Create readonly tuple types:
// Without as const
const colors1 = ["red", "green", "blue"];
// Type: string[]
// With as const
const colors2 = ["red", "green", "blue"] as const;
// Type: readonly ["red", "green", "blue"]
// colors2[0] = "yellow"; // ✗ Error: Cannot assign
// colors2.push("purple"); // ✗ Error: push doesn't exist
// Use in function parameters
function printColor(color: typeof colors2[number]) {
console.log(color);
}
printColor("red"); // ✓ Valid
printColor("green"); // ✓ Valid
// printColor("yellow"); // ✗ Error: not in tuple
🔹 Deep Readonly with Const Assertions
Make nested objects immutable:
const config = {
api: {
url: "https://api.example.com",
timeout: 5000,
endpoints: {
users: "/users",
posts: "/posts"
}
},
features: {
darkMode: true,
notifications: false
}
} as const;
// All properties are deeply readonly
console.log(config.api.url); // ✓ Can read
// config.api.timeout = 3000; // ✗ Error
// config.features.darkMode = false; // ✗ Error
// config.api.endpoints.users = "/people"; // ✗ Error
🔹 Const Assertions for Enums
Create enum-like objects with const assertions:
// Instead of enum
const Status = {
PENDING: "pending",
APPROVED: "approved",
REJECTED: "rejected"
} as const;
// Extract the type
type StatusType = typeof Status[keyof typeof Status];
// Type: "pending" | "approved" | "rejected"
function updateStatus(status: StatusType) {
console.log(`Status updated to: ${status}`);
}
updateStatus(Status.PENDING); // ✓ Valid
updateStatus("approved"); // ✓ Valid
// updateStatus("unknown"); // ✗ Error: not a valid status
🔹 Readonly vs Const Assertions
Understand the differences:
// readonly: Type-level immutability
type Config1 = {
readonly url: string;
readonly port: number;
};
const config1: Config1 = {
url: "localhost",
port: 3000
};
// config1.port = 8080; // ✗ Error
// as const: Value-level immutability + literal types
const config2 = {
url: "localhost",
port: 3000
} as const;
// Type: { readonly url: "localhost"; readonly port: 3000 }
// config2.port = 8080; // ✗ Error
// config2 has literal types, config1 has general types
🔹 Practical Example: Configuration
Build type-safe, immutable configuration:
const APP_CONFIG = {
app: {
name: "MyApp",
version: "1.0.0",
environment: "production"
},
api: {
baseUrl: "https://api.example.com",
timeout: 5000,
retries: 3
},
features: {
authentication: true,
analytics: true,
darkMode: false
},
routes: {
home: "/",
login: "/login",
dashboard: "/dashboard"
}
} as const;
// Extract types
type AppConfig = typeof APP_CONFIG;
type Environment = typeof APP_CONFIG.app.environment;
type Route = typeof APP_CONFIG.routes[keyof typeof APP_CONFIG.routes];
function navigate(route: Route) {
console.log(`Navigating to: ${route}`);
}
navigate(APP_CONFIG.routes.home); // ✓ Valid
navigate("/dashboard"); // ✓ Valid
// navigate("/unknown"); // ✗ Error
🔹 Readonly with Generics
Create generic readonly utilities:
// Deep readonly utility
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
type User = {
id: number;
profile: {
name: string;
settings: {
theme: string;
notifications: boolean;
};
};
};
type ReadonlyUser = DeepReadonly<User>;
const user: ReadonlyUser = {
id: 1,
profile: {
name: "Alice",
settings: {
theme: "dark",
notifications: true
}
}
};
// user.profile.settings.theme = "light"; // ✗ Error: deeply readonly
💡 Key Takeaways
- readonly modifier prevents property reassignment
- as const creates literal types and deep immutability
-
Use
ReadonlyArray<T>orreadonly T[]for immutable arrays - Const assertions are perfect for configuration objects
-
Combine with
typeofandkeyoffor type extraction - Immutability helps prevent bugs and makes code more predictable