TypeScriptのnever型とは?使いどころと網羅性チェックを整理
TypeScriptで never 型に出くわすのは、たいてい「Type ‘x’ is not assignable to type ‘never’」という赤いエラーが出て、その意味を調べているときではないでしょうか。
never 型は「絶対に値が存在しない」ことを表す型で、正しく使えば union 型の分岐漏れをコンパイル時に検出できる強力な仕組みになります。この記事では、never の意味と自然に現れる場面、そして never を使って安全なコードを書くための網羅性チェックの手順を、動くコードと実際のエラー文とともに整理します。
この記事は次のような方におすすめです。
- union 型など基本的な型は理解していて、次のステップに進みたい方
- 「Type ‘x’ is not assignable to type ‘never’」の意味を知りたい方
- switch 文の分岐漏れを型エラーとして防ぎたい方
- neverとvoid・unknownの違いをすっきり整理したい方
読み終えるころには、never を「避けるべきエラー」ではなく「分岐漏れを教えてくれる味方」として使いこなせるようになっているはずです。
それでは、順を追って詳しく見ていきましょう!
- 未経験で後悔したくない
【実体験】未経験からITエンジニアに転職して後悔した話|4社経験してわかった「最初の選択ミス」 - 年収が低くて不安
エンジニア転職体験談|4社で年収250→510万にした全記録
never型とは何か|「値が存在しない」を表すボトム型
never 型は、値が絶対に存在しないことを表す型です。「まだ値が入っていない」でも「空文字や 0」でもなく、そこに入りうる値が原理的にひとつもない、という状態を表します。
型を「その型に属する値の集合」として捉えると、string は文字列全体の集合、boolean は true と false の集合です。この見方でいうと、never は要素をひとつも持たない空集合にあたります。あらゆる型の一番下に位置することから、never は型システムの底を意味するボトム型と呼ばれます。だからこそ never は「正常に完了しない処理」や「もう起こりえない状態」を表すときに役立ちます。
neverには何も代入できない(唯一never自身のみ)
never 型の変数には、値を代入できません。試しに数値を入れてみると、その場で型エラーになります。
const x: never = 1;
// 例:TS2322 Type 'number' is not assignable to type 'never'.
理由は、never が空集合だからです。「never 型の値」というものが存在しないため、1 も "a" も true も、never には当てはまりません。逆に言えば、never へ代入できるのは never 型の値のときだけです。実際にはそのような値を作る手段がないため、代入はまず成立しないと考えてよいでしょう。
この「入れられなさ」は欠陥ではなく機能です。「本来ここには何も来ないはず」という前提をコンパイラに宣言するのが never であり、その前提が崩れたときにエラーとして知らせてくれます。この性質により、未処理の値が残っていれば型エラーとして検出できます。
anyやunknownとの位置づけ(top型とbottom型)
unknown が「何でも入る型」だとすると、never はその正反対にあたります。両者は型システムの上下の端として、きれいに対称をなしています。
unknown(トップ型) … どんな値も代入できる。すべての型の“親”
▲ 代入できる向き
│
各種の型(string, number, ...)
│
▼ 代入できる向き
never(ボトム型) … どんな型へも代入できる。すべての型の“子”
- unknown はトップ型:あらゆる値を受け取れますが、そのままでは使えず、絞り込みが必要です。
- never はボトム型:
const s: string = neverValue;のようにどんな型の変数へも代入できる一方で、never 自体には値を代入できません。
つまり never は「すべての型のサブタイプ」という向きを持ちます。「never は何にでも代入できるが、never には何も代入できない」——この非対称を押さえると、never 関連の代入エラーを読み解きやすくなります。
never型はどこに現れるのか|3つの登場シーン
never は自分で : never と書かなくても、コードの中に自然に現れます。主な登場シーンは次の3つです。
- 常に例外を投げて、正常に値を返さない関数
- 無限ループなどで、決して終わらない関数
- 型の絞り込みが進み、候補が尽きた分岐
いずれも共通しているのは「この先に到達する値が存在しない」という状況です。
常に例外を投げる関数の戻り値
常に throw する関数、つまり呼ぶと必ず例外で処理が中断する関数の戻り値型は never になります。
function fail(msg: string): never {
throw new Error(msg);
}
この関数は正常に値を返して終わることが決してないため、「返ってくる値」が存在しません。そのため、存在しない戻り値を表す型として never が使われます。
戻り値型を省略したときの推論には差がある点に注意してください。関数式やアロー関数では never と推論される一方、function 宣言では void と推論される場合があります(TypeScriptのバージョンや設定により挙動が異なる場合あり)。意図を明確にしたいなら : never と明示的に注釈するのが確実です。エラーを集約するヘルパー関数などで、この never 注釈が役立ちます。
決して終わらない関数(無限ループ)
無限ループを含み、そこから抜けない関数も never になります。
function loop(): never {
while (true) {
// 何らかの常駐処理
}
}
ポイントは、undefined を返す関数との違いです。戻り値のない関数は undefined という値を返して制御が戻ってくるのに対し、この関数はそもそも制御が呼び出し元に戻りません。「戻り値がない」のではなく「戻ってくること自体がない」——この違いを表すために never が使われます。
型の絞り込みで候補が尽きたとき
union 型を typeof などで絞り込んでいくと、すべての可能性を除外した分岐で型が never に縮みます。
function handle(x: string | number) {
if (typeof x === "string") {
// ここでの x は string
} else if (typeof x === "number") {
// ここでの x は number
} else {
// string でも number でもない → x は never
x; // エディタでホバーすると never と表示される
}
}
string | number から string と number を取り除くと、残る候補はひとつもありません。「もう来るはずのない分岐」であることを、TypeScript は never という型で表現します。エディタで最後の x にカーソルを合わせると never と表示され、この分岐に到達する値がないことが視覚的に確認できます。
網羅性チェックで分岐漏れを防ぐ(never の主用途)
ここまでは「勝手に現れる never」でしたが、never を意図的に使ってコードを安全にするのが網羅性チェックです。狙いはシンプルで、union 型のすべてのケースを処理したかどうかを、型で保証することにあります。
考え方は次の流れです。
union の全ケースを処理
残った値を never に代入
未処理があればコンパイルエラー
前の絞り込みで見たとおり、全ケースを処理し切った分岐では変数の型が never に縮みます。そこにあえて never への代入を書いておけば、処理し忘れたケースが残っている限り型が never にならず、エラーになるという仕掛けです。
A | B の型)の基本は、こちらで基礎から整理しています。
switch 文で never に代入して未処理を検知する
具体例で見てみましょう。kind プロパティで種類を区別する図形の union を、面積を計算する関数で処理します。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
default:
const _exhaustive: never = shape; // 全ケース処理済みなら shape は never
return _exhaustive;
}
}
circle と square の両方を処理し切ると、default に到達する shape の型は never に縮みます。そのため const _exhaustive: never = shape; はエラーなく通ります。ここで never への代入が問題なく通ることが、「全ケースを処理できている」という型レベルの証明になります。default で never へ代入するこの一行が、網羅性チェックの本体です。
ケースを追加すると即エラーで気づける効果
この仕組みの真価は、後から union にケースを追加したときに現れます。三角形を足してみましょう。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "triangle"; base: number; height: number }; // 追加
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
default:
const _exhaustive: never = shape;
// 例:TS2322 Type 'Triangle' is not assignable to type 'never'.
// (型名・文言はTypeScriptバージョンにより異なる場合あり)
return _exhaustive;
}
}
triangle の case を書き足さないまま union にメンバーを増やすと、default に到達しうる shape に triangle が残ります。すると型が never に縮まなくなり、never への代入がその場でコンパイルエラーになります。
これは単なるエラーではなく、「処理を追加し忘れているよ」という、未来の自分やチームへの型システムからの警告です。ケースの追加とハンドリングの追加が食い違えば、実行するまでもなくビルド時点で気づけます。分岐漏れを人間の注意力だけに頼らず、型システムで見張れる点が、この手法の価値です。
assertNever関数でパターン化する
毎回 const _exhaustive: never = shape; と書くのは冗長です。この定型を関数に切り出して再利用しましょう。
function assertNever(x: never): never {
throw new Error("Unexpected value: " + JSON.stringify(x));
}
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
default:
return assertNever(shape); // 未処理ケースがあればここで型エラー
}
}
assertNever は引数の型を never に固定してあるため、未処理のケースが残っていると引数の型が合わず型エラーになります。仕組みは手書きの never 代入と同じですが、関数化することで次の利点が生まれます。
- DRY:網羅性チェックの定型を一箇所にまとめ、各 switch で使い回せる
- 実行時にも保険が効く:想定外の値が万一届いたときは、例外を投げて早期に気づける
この assertNever を使ったパターンは、公式ドキュメントで網羅性チェックの手法として紹介されています(TypeScript Handbook「Unions and Intersection Types」Union Exhaustiveness checking)。union を扱う switch には default: assertNever(x) を添える、と覚えておくと安心です。
neverとvoid・undefinedの違いを整理する
never は、似て見える void・undefined・そして対極にある unknown としばしば混同されます。それぞれ意味も使い道も異なるので、4つを1枚の表で対比しておきましょう。
| 型 | 意味 | その型に代入できる値 | その型の値を代入できる先 | 典型用途 |
|---|---|---|---|---|
| never | 値が存在しない(ボトム型) | never のみ(実質不可) | すべての型へ代入できる | 網羅性チェック・throw する関数の戻り値 |
| void | 戻り値を使わない | undefined など限定的 |
他の型へはほぼ代入できない | 戻り値を持たない関数の戻り値型 |
| undefined | 「未定義」という単一の値 | undefined のみ |
strict 設定では限定的 | 値がないことの明示 |
| unknown | 何でも入る(トップ型) | すべての値を代入できる | unknown / any 以外へ使うには絞り込みが必要 | 型が不明な外部入力の受け口 |
とくに混同しやすい never と void の核心はこう整理できます。void は「呼び出し側が戻り値を使わない」ことを表す戻り値型です。一方、never は「観測できる戻り値がなく、例外送出やプロセス終了などで通常の制御フローに戻らない」ことを表します。console.log のような戻り値を気にしない関数は void、必ず例外を投げるヘルパーは never、と使い分けます。
実務でneverを扱うときの注意点
never は網羅性チェックのように狙って使うと便利ですが、意図せず現れて型が消え、原因が分からず戸惑うこともあります。「書いた覚えのない never」に遭遇したときにあわてないよう、代表的なパターンを押さえておきましょう。
交差型やMapped Typeで意図せずneverになる
交差型(A & B)は「両方を満たす型」を意味します。そのため、両立しえない型を交差させると、条件を満たす値がなくなり never になります。
type Impossible = string & number;
// ホバーすると Impossible は never と表示される
const v: Impossible = "a";
// 例:TS2322 Type 'string' is not assignable to type 'never'.
string かつ number である値は存在しないため、その積は空集合=neverに潰れます。同じことは、条件でプロパティを絞り込むうちに全要素が落ちてしまったマップ型(既存の型からプロパティを変換して新しい型を作る仕組み)でも起こります。「型が never になった」ときは、どこかで矛盾した条件を掛け合わせて、集合が空になっていないかを疑うのが第一歩です。
Excludeなどユーティリティ型の内部で使われるnever
意図せぬ never は厄介ですが、実は標準のユーティリティ型はこの「never にすると消える」性質を積極的に利用しています。代表例が、union から特定の型を取り除く Exclude です。
type Exclude<T, U> = T extends U ? never : T;
type A = Exclude<"a" | "b" | "c", "b">; // "a" | "c"
Exclude<T, U> は、T の各メンバーについて「U に含まれるなら never に、そうでなければそのまま」と振り分けます。never になったメンバーは union から自動的に取り除かれるため、結果として U を除外した型が得られます。union の各要素へ順番に適用される条件付き型と、「消したいものは never にする」という発想の組み合わせです。never が単なるエラーの元ではなく、型を操作するための部品として使われていることが分かります。
【付録】さらに学びを深めるためのリソース
さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。
おすすめの書籍
ゼロからわかる TypeScript入門
技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。
変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。
プロを目指す人のためのTypeScript入門
技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。
オンラインで参照できる公式ドキュメント
TypeScript公式ハンドブック
https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。
TypeScript Deep Dive
https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。
TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。
まとめ – never の意味と網羅性チェックで安全に
TypeScript の never 型について整理してきました。要点は次のとおりです。
- never は「値が絶対に存在しない」ことを表すボトム型で、never には値を代入できないが、never はどんな型へも代入できる
- never は常に例外を投げる関数・終わらない関数・絞り込みで候補が尽きた分岐に自然に現れる
- never の主用途は網羅性チェック。union の全ケースを処理し切ると型が never に縮む性質を使い、分岐漏れをコンパイルエラーとして検出できる
- void は「正常に終わるが戻り値を使わない」、never は「そもそも正常に終わらない」という違い
- 交差型やユーティリティ型(
Excludeなど)でも、「矛盾=never で消す」仕組みが働いている
まず持ち帰るなら、union を分岐する switch には default: assertNever(x) を1行添えることです。これだけで、ケースの追加漏れを未来の型エラーとして受け取れるようになります。
※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。






