引数を readonly User[] にしたのに、関数の中で users[0].name = "x" と書けてしまいました。なぜですか。
readonly が守るのは配列そのもので、要素のオブジェクトのプロパティまでは守りません。要素も守るなら Readonly<User> を重ねます。readonly は型だけの仕組みで、実行時には何もしません。
面接官が見ているのはreadonly が浅いことと、型だけの仕組みであることを知っているか。
これは「上位3件を返す関数 top3(users: User[]) の中で users.sort(...) を呼んでいたため、呼び出し側の一覧の並び順まで変わっていました。型でどう防ぎますか。」への追撃質問です。
よくある答えと、面談でどう見られるか
- readonly が守るのは配列そのもので、要素のオブジェクトのプロパティまでは守らない。要素も守るなら Readonly<User> などを重ねる
その通りです。readonly は浅く、しかも実行時には何もしません。 - readonly は実行時に効くので、実行すればエラーになる
実行時には何もしません。型チェックで止めるだけです。 - TypeScript の不具合
仕様どおりです。readonly は付けた階層だけに効きます。 - readonly ではなく const にすれば中まで守られる
const は変数の再代入を禁じるだけで、中身はまったく守りません。
模範解答
readonly User[] が禁じるのは、配列そのものの書き換え(要素の入れ替えや追加・削除)だけです。要素の User オブジェクトのプロパティは、別の階層なので守られません。
公式のハンドブックも、readonly は値が完全に不変であることを意味しない、と注意しています。要素まで守りたいなら、readonly Readonly<User>[] のように要素の型にも付けます。Readonly<T> も1階層だけなので、入れ子のオブジェクトがあれば、その分も必要です。
また、readonly は型チェックだけの仕組みで、実行時には何も止めません。型を経由しない書き換え(any を通したものなど)は防げないので、本当に凍結したいなら Object.freeze のような実行時の手段が別に要ります。
対象: TypeScript 5.x〜7.0 照合した一次資料: 公式ドキュメント 照合: 2026-10-25 最終更新: 2026-10-25
お使いの版で挙動が違う場合があります。誤りに気づいたらこの解説を報告するから教えてください。