TypeScript Type Challenge Readonly Walkthrough

| 1 min read

The goal of the challenge is to reimplement the built in Readonly generic type that exists in TypeScript, without using Readonly itself.

The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00007-easy-readonly/README.md

Watch the video

An example case

Given the following TypeScript:

interface Todo {
title: string
description: string
}


const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}

We need to create a new type called MyReadonly that will result in the following operations failing,

todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property

This means we need to create a generic type that accepts a type as a parameter, and returns a new type with the readonly property set for each property in that type.

type MyReadonly<T> = ?

Approach

We need to create a new type based off an existing type, this means we need to use mapped types in TypeScript.

The basic syntax for a mapped type is:

type MyReadonly<T> = {
[Property in keyof T]: T[Property]
}

Here we use keyof T to generate a union type of all the properties of T, which we then use to iterate over and generate a new type from. The type that the above returns is exactly the same as the type T passed in.

The solution

Mapped types allow you to add or remove modifiers when you create new types. These modifiers are ? to control optionality and readonly to control mutability.

You can add them with + and remove them with -.

Therefore we can add the readonly by prefixing each entry in our new type with the +readonly modifier.

type MyReadonly<T> = {
+readonly [Property in keyof T]: T[Property]
}

However, when adding modifiers, the + is optional, so we can simplify this type to:

type MyReadonly<T> = {
readonly [Property in keyof T]: T[Property]
}