TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
143: Readonly Array Types
You've likely dealt with the frustration of a bug where some random function deep in your codebase modified an array that was supposed to be a constant. In JavaScript, const prevents the variable from being reassigned, but it doesn't stop you from pushing, popping, or splicing the array itself. That's where ReadonlyArray comes in.
Protecting our app constants
Let's build a simple permission system. I want a fixed list of roles that our application supports. Since these are defined by the business logic, no part of the app should be able to change this list at runtime. It should be a "source of truth."
const SUPPORTED_ROLES = ['admin', 'editor', 'viewer'];
function checkRole(role: string) {
return SUPPORTED_ROLES.includes(role);
}
At first glance, this looks fine. I used const, so I can't do SUPPORTED_ROLES = []. But there's a loophole.
Where I accidentally broke the config
While building out a feature to "temporarily" allow a guest role during a beta test, I wrote a helper function. I thought I was just adding a role for the current session, but I accidentally mutated the global constant.
function enableGuestAccess() {
// I'm just adding one role... right?
SUPPORTED_ROLES.push('guest');
}
enableGuestAccess();
console.log(SUPPORTED_ROLES); // ['admin', 'editor', 'viewer', 'guest']
TypeScript didn't complain. Why? Because SUPPORTED_ROLES was inferred as string[]. A standard array is mutable by definition. This is a disaster waiting to happen in a large project; some utility function could wipe out your configuration, and you'd spend hours hunting down the mutation.
Locking it down with ReadonlyArray
To fix this, I need to tell TypeScript that this array is not just a constant variable, but a constant collection. I can use the ReadonlyArray<T> generic or the readonly T[] shorthand.
const SUPPORTED_ROLES: readonly string[] = ['admin', 'editor', 'viewer'];
function enableGuestAccess() {
// TypeScript now throws an error:
// Property 'push' does not exist on type 'readonly string[]'.
SUPPORTED_ROLES.push('guest');
}
Now the compiler has my back. It strips away all the mutating methods—push, pop, shift, unshift, splice, sort, and reverse—leaving only the read-only methods like map, filter, and includes.
A quick note on syntax
You'll see two ways to write this in the wild. They are functionally identical:
readonly string[]: The shorthand. I prefer this because it reads naturally from left to right.ReadonlyArray<string>: The generic form. This is useful if you're already using other generics in a complex type definition.
One thing to keep in mind: readonly` is a compile-time check. If you're interacting with a vanilla JS library that doesn't care about your TypeScript types, it could still technically mutate the array. But for 99% of your internal app logic, this is the gold standard for preventing state bugs.
📋 Practical Task
Fixing the Immutable Product Catalog
You are working on an e-commerce site. There is a BASE_CATEGORIES array that should never be changed by the UI components. However, a junior developer wrote a function that attempts to add a new category to this list, and currently, TypeScript is allowing it.
Your Task: Modify the type definition of BASE_CATEGORIES so that the addCategory function triggers a TypeScript compilation error, preventing the mutation of the catalog.
const BASE_CATEGORIES = ['Electronics', 'Books', 'Clothing'];
function addCategory(category: string) {
BASE_CATEGORIES.push(category);
}
addCategory('Home & Garden');There are no comments for now.