Skip to Content
Course content

143: Readonly Array Types

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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');
Rating
0 0

There are no comments for now.

to be the first to leave a comment.