TypeScriptのinterfaceとtypeの違いって?3つの違いと使い分け方
TypeScriptで型を書くとき、type と interface のどちらでも同じように書けてしまい、「結局どっちを使えばいいの?」と手が止まった経験はありませんか。
実は実務でまず押さえるべき差は主に3つで、その3つさえ押さえれば「迷ったらこれ」と即決できる判断軸が手に入ります。他にもエラー表示や型チェックの性能面の差はありますが、判断軸として重要なのはこの3点です。この記事では、3つの違いと選び方を具体例で整理します。
この記事は次のような方におすすめです。
- typeもinterfaceも書けるが「どっちを使うべきか」で迷っている人
- 違いを暗記ではなく、設計の判断軸として理解したい人
- 宣言マージや拡張で予想外の挙動にハマったことがある人
- 公式推奨とコミュニティの「typeで統一」のズレが気になる人
読み終えるころには、目の前の型を type で書くべきか interface で書くべきかを、根拠を持って一発で決められるようになります。
それでは、順を追って詳しく見ていきましょう!
- 未経験で後悔したくない
【実体験】未経験からITエンジニアに転職して後悔した話|4社経験してわかった「最初の選択ミス」 - 年収が低くて不安
エンジニア転職体験談|4社で年収250→510万にした全記録
まず結論|typeとinterfaceはどう違い、どう選ぶか
type と interface の実質的な違いは、突き詰めると次の3点だけです。
- 宣言マージ:同名で再宣言したときに合算できるか(interfaceは可、typeは不可)
- 表現できる型の幅:オブジェクトの形以外(union・タプル・プリミティブの別名など)に名前を付けられるか(typeは可、interfaceはオブジェクト形状中心)
- 拡張構文:
extendsで継承するか、交差型&で合成するか
ざっくりした選び方も先に出しておきます。両者の性格は、次の表に集約できます。
| 仕組み | ひとことで言うと |
|---|---|
| type | 幅広い型に名前を付ける汎用エイリアス |
| interface | オブジェクトの形を宣言し、後から同名宣言で拡張できる構文 |
オブジェクトの形だけを書くならどちらでも書けて、結果もほぼ変わりません。差が効いてくるのは「同名で追記したい」「オブジェクト以外の型に名前を付けたい」「拡張時の衝突挙動が気になる」という場面です。
typeとは|型に名前を付ける汎用の仕組み
type(型エイリアス)は、既存の型に別名を付けるための仕組みです。オブジェクトの形だけでなく、union・タプル・プリミティブなど、あらゆる型に名前を付けられます。
// オブジェクト型に名前を付ける
type User = {
id: number;
name: string;
};
// union 型に名前を付ける
type Status = "active" | "inactive" | "pending";
// プリミティブの別名
type UserId = number;
ここで押さえたいのは、型エイリアスは実行時の値を一切作らないという点です。type User = ... と書いても、JavaScriptに変換された時点で消えてなくなり、コンパイル時の「別名」として働くだけです。クラスやオブジェクトのように実体が残るわけではありません。
Status のような "active" | "inactive" といった複数の候補のいずれかを表す型はinterfaceでは表現できません。typeは「オブジェクトの形」という枠を超えて型に名前を付けられるところが、interfaceとの役割上のいちばん大きな違いです。
interfaceとは|オブジェクトの形を宣言する構文
interface は、オブジェクトがどんなプロパティを持つかという「形」を宣言する構文です。typeのオブジェクト型とよく似ていますが、書き味と狙いが少し違います。
interface User {
id: number;
name: string;
}
// クラスに「この形を満たすこと」を約束させる
class Admin implements User {
id = 1;
name = "admin";
role = "admin";
}
interfaceは implements でクラスに実装を約束させる用途と相性がよく、オブジェクトの形やpublicなAPIの型を宣言するのに向いています。
もう一つ、typeには無い性質があります。同名の interface をあとからもう一度宣言すると、プロパティが追記(合算)されるという挙動です。
interface User {
id: number;
}
interface User {
name: string;
}
// User は { id: number; name: string } として扱われる
この「あとから同名宣言で拡張できる」性質は、ライブラリの型を利用側で拡張するときなどに効いてきます。
実際に効いてくる3つの違いを比較表で一気に把握
まず押さえるべき違いは、冒頭で挙げた3点に絞り込めます。表で並べると、それぞれの得意・不得意がはっきりします。
| 違い | interface | type |
|---|---|---|
| 宣言のマージ(同名で追記) | できる(プロパティが合算される) | できない(重複エラー) |
| 表現できる型の幅 | オブジェクトの形が中心 | union・タプル・プリミティブなどにも名前を付けられる |
| 拡張構文 | extends で継承 |
交差型 & で合成 |
実務で最初に判断軸として使うなら、この3点を押さえておけば十分です。表現力や拡張の性質に注目すると、どちらを選ぶべきか判断しやすくなります。
違い1|宣言のマージ:interfaceは同名で追記できる、typeはできない
同名で2回宣言したとき、interfaceはプロパティを合算し、typeは重複エラーになります。
interface Animal {
name: string;
}
interface Animal {
legs: number;
}
const cat: Animal = { name: "cat", legs: 4 }; // OK:2つの宣言が合算される
同じことをtypeでやると、再宣言できずにエラーになります。
type Animal = {
name: string;
};
type Animal = { // 例:TS2300 Duplicate identifier 'Animal'.(TypeScriptバージョンにより文言が異なる場合あり)
legs: number;
};
この宣言マージは、自分のコードでは使う場面が限られますが、外部ライブラリの型をあとから拡張したいときに役立ちます。たとえば既存の型に独自プロパティを足したい、といったケースです。一方で、意図せず同名interfaceを書いてプロパティが勝手に増えてしまう事故も起こりうるので、合算される性質は把握しておきましょう。
違い2|定義できる型の幅:typeはunion・タプル・プリミティブにも名前を付けられる
interfaceで書けず、typeでだけ書けるものの代表が、union型・タプル型・プリミティブの別名です。
type Result = "success" | "error"; // union
type Pair = [number, string]; // タプル
type Id = string; // プリミティブの別名
これらはオブジェクトの「形」ではないため、interfaceでは同等のものを書けません。interfaceが宣言できるのは、あくまでオブジェクトのプロパティ構造が中心です(なお関数型は、typeで書くことが多いものの、interfaceでもcall signatureで表現できます)。
ただし条件付きの例外があります。オブジェクトの形だけを書くなら、typeでもinterfaceでも書けて結果も実質同じです。つまり「typeの方が表現力が広い」のはあくまでオブジェクト以外を含む場合の話で、単純なオブジェクト型ではどちらでも構いません。
違い3|拡張の書き方:継承構文と交差型の衝突時の挙動
拡張のときも書き方が分かれます。interfaceは extends で継承し、typeは交差型 & で合成します。普通に足し算するぶんには、どちらもほぼ同じ結果になります。
interface Base {
id: number;
}
interface WithName extends Base {
name: string;
}
type BaseT = { id: number };
type WithNameT = BaseT & { name: string };
差が出るのは、同名プロパティを互換性のない型で衝突させたときです。interfaceの extends は宣言した時点でエラーになります。
interface A {
value: string;
}
interface B extends A {
value: number; // 例:TS2430 系 Interface 'B' incorrectly extends interface 'A'.(バージョンにより文言が異なる場合あり)
}
一方、交差型 & は宣言時点ではエラーにならず、衝突したプロパティが never 型になり、実際に値を入れようとした利用時に破綻します。
type A = { value: string };
type B = A & { value: number };
// value は string & number = never になる
const b: B = { value: "x" }; // 例:TS2322 系 Type 'string' is not assignable to type 'never'.
const c: B = { value: 1 }; // 例:TS2322 系 Type 'number' is not assignable to type 'never'.
// string も number も never には入らず、値を代入する箇所で弾かれる(バージョンにより文言が異なる場合あり)
衝突をその場で気づきたいなら extends、後で破綻するのが交差型 & という違いです。extends には継承以外に型制約や条件型としての用法もあり、書き方によって意味が変わります。
補足|インデックスシグネチャへの代入で弾かれることがある
意外と知られていないのが、interfaceで定義した型は Record<string, unknown> のようなインデックスシグネチャを持つ型へ代入すると弾かれることがあるという挙動です。typeで同じ形を書くと通る場合があり、ここで「同じ形なのに片方だけエラー」と混乱しがちです。
interface Point {
x: number;
y: number;
}
type PointT = { x: number; y: number };
const a: Record<string, unknown> = {} as Point; // 例:TS2322 系で弾かれる場合がある
const b: Record<string, unknown> = {} as PointT; // OK になりやすい
これは、interface型が「暗黙のインデックスシグネチャ」を持たないと見なされることに由来します(この互換性判定の細部はバージョンや文脈により異なるため、実際のエラーは手元で確認してください)。回避したいときは、interface側に明示的なインデックスシグネチャを足すのが一つの方法です。
interface Point {
x: number;
y: number;
[key: string]: unknown; // 明示的に index signature を付ける
}
どちらを使うべきか|判断フローと公式・実務の指針
結局どちらを選ぶべきかは、次の判断フローを上から当てはめれば決まります。
- union・タプル・関数・条件型などに名前を付けたい?
Yes なら type 確定 - publicなAPIの型・後から拡張する余地・class実装が必要?
Yes なら interface - オブジェクトの形だけ?
どちらでも可。プロジェクトの規約に合わせる
上から順に見て、最初に Yes になったところで決めれば迷いません。「オブジェクトの形だけならどちらでもよく、それ以外の要素が混じった瞬間に選択肢が絞られる」と捉えると、判断がぶれにくくなります。
公式はinterface寄り、コミュニティはtype寄りという“ズレ”
「公式はinterface推し」「でも現場ではtypeで統一をよく聞く」と、情報が食い違って見えるのには理由があります。
TypeScript公式のハンドブックは、型エイリアスとインターフェースの違いを説明したうえで、多くのケースでどちらを選んでもよいとしつつ、まずはinterfaceを使い、必要になったらtypeに切り替えるという方針を一つの目安として挙げています(TypeScript Handbook「Everyday Types」Differences Between Type Aliases and Interfaces)。大きな交差型よりインターフェースの継承の方が型チェック上有利になりやすい、というパフォーマンス観点の指摘も知られています。
一方で、union・関数・タプルなどを含む型はtypeでしか書けないため、「どうせ混在するならtypeに寄せて一貫性を取る」という選び方もできます。表現力の広さと書き方の統一を重視するなら、type中心の方針が合う場面もあるわけです。
どちらかが間違いというより、重視するもの(後から拡張しやすいか/表現力と一貫性か)で答えが変わると捉えるのが実態に近いです。だからこそ、暗記ではなく判断フローで決めるのが安全です。
迷ったときの最終判断軸
一言にまとめると、オブジェクトの形だけならinterfaceでもtypeでも可、union・タプル・関数などを含む瞬間にtypeで確定、ライブラリ型の拡張や宣言マージが要るならinterfaceです。
この3行だけ覚えておけば、目の前の型でほぼ事故りません。形以外の要素が型に入った時点で選択肢は自然に絞られるので、まずは「オブジェクトの形だけかどうか」を最初に見るのがコツです。
【付録】さらに学びを深めるためのリソース
さらに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プログラマーになれるはずです。
まとめ – 違いは3つ、迷ったら判断フローで即決
この記事では、typeとinterfaceの違いと使い分けを整理しました。
- 実質的な違いは宣言マージ・表現できる型の幅・拡張構文の3点だけ
- interfaceは同名で追記でき、typeはできない
- typeはunion・タプル・プリミティブなどオブジェクト以外にも名前を付けられる
- 拡張は
extends(衝突を宣言時に検知)と交差型&(衝突はnever化して利用時に破綻)で挙動が違う - 迷ったら「形だけなら両方可/それ以外を含むならtype/拡張・宣言マージが要るならinterface」で決める
違いを暗記するのではなく、判断フローを上から当てはめれば、どちらを使うかはその場で決められます。
※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。






