IT-Q

引数を 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
お使いの版で挙動が違う場合があります。誤りに気づいたらこの解説を報告するから教えてください。