Readonly Typescript Interfaces Done Right

One of the main reasons I love typed languages like Typescript is that they allow a developer to reveal how their code should be used through the use of interfaces.

For example, it's simple to reveal what arguments should be passed to a function, the shape of those arguments, and, one oft overlooked feature, the ability to mark properties of objects or even whole objects as read-only. The ability to mark objects as read-only (or quasi immutable) is especially powerful when building functional programs, eg. a React application.

Like all parts of programming, there's one way and then there's the right way.

First let's look at how not to mark an entire object as read-only:

interface Props {
  readonly firstName: string
  readonly lastName: string 
}

const props: Props = {
  firstName: 'Brian',
  lastName: 'Gonzalez'
}

props.firstName = 'Jose' // Won't compile

There's a better way.

We can use Typescript's built-in Readonly<T> generic type to achieve the same effect with less visual noise or cognitive overhead:

interface Props {
  firstName: string
  lastName: string 
}

const props: Readonly<Props> = {
  firstName: 'Brian',
  lastName: 'Gonzalez'
}

props.firstName = 'Jose' // Won't compile

A few caveats: if you need to mark only bits of your object as immutable, dont use Readonly<T>. Also, Readonly<T> does not reach into nested objects, so you'll need to wrap those objects in Readonly<{}> accordingly.

Update

As pointed out by reader schlenkster on Reddit, this method only marks a per-use instance as readonly. We can get around this nuance by extending Readonly<T> at definition time:

interface Props extends Readonly<{
  firstName: string
  lastName: string 
}> { }

const props: Props = {
  firstName: 'Brian',
  lastName: 'Gonzalez'
}

props.firstName = 'Jose' // Won't compile