TypeScriptのtypeとinterfaceを比較・検証する 違いと使い分けの判断基準を整理
TypeScript で型を定義するとき、type(型エイリアス)と interface(インターフェース)のどちらを使うべきか迷った経験はないでしょうか。両者はオブジェクト型を定義できる点では似ていますが、拡張性や型演算の可否といった決定的な違いがあります。本記事では、公開 API やライブラリ設計で求められる拡張性と、内部ロジックで必要な型安全を両立させるための判断基準を、実際の検証結果と失敗談を交えて整理します。
type と interface の違い 機能比較表
まず、型エイリアスとインターフェースの主要な違いを一覧で確認します。
| 機能 | type(型エイリアス) | interface(インターフェース) |
|---|---|---|
| 宣言のマージ | 不可 | 可能 |
| 拡張方法 | &(交差型) | extends |
| ユニオン型 | 可能 | 不可 |
| プリミティブ別名 | 可能 | 不可 |
| 条件型・マッピング型 | 可能 | 不可 |
| クラスの implements | 可能 | 可能 |
| IDE 補完の見やすさ | 型計算が複雑だと長い | 直感的 |
この表だけでは判断が難しい場面も多いため、後述の具体例と判断基準を参照してください。
検証環境
- OS: macOS Sequoia 15.3
- Node.js: 24.1.0 (LTS)
- TypeScript: 5.9.3
- 主要パッケージ:
- @types/node: 24.0.0
- 検証日: 2026 年 01 月 23 日
背景 なぜ type と interface の使い分けが問題になるのか
TypeScript には型を定義する手段として type(型エイリアス)と interface(インターフェース)の 2 つが存在します。型エイリアスは TypeScript 1.4 で導入され、インターフェースは言語設計当初から存在する古参の機能です。
歴史的には「オブジェクト型は interface、それ以外は type」という暗黙のルールがありました。しかし TypeScript の進化に伴い、両者の機能差は縮小しつつも、依然として拡張性と型演算という軸で明確な違いが残っています。
つまずきやすい点:両者の違いを「どちらでも書ける」と捉えて混在させると、チーム内で書き方がバラつき、レビューコストが増大します。
実務で問題になる 2 つの観点
型安全な設計を目指すとき、判断軸は大きく 2 つに分かれます。
-
公開 API・ライブラリとしての拡張性
- 利用者が型を拡張できるか
- 破壊的変更を避けられるか
-
内部ロジックでの型安全と表現力
- ユニオン型や条件型を使えるか
- 型レベルで分岐処理を記述できるか
この 2 軸を意識せずに「なんとなく interface」や「なんとなく type」で統一すると、後から設計を変更せざるを得なくなります。
課題 type と interface の選択ミスが引き起こす問題
ここでは、検証と実務で実際に遭遇した問題を紹介します。
課題 1 宣言マージを知らずにライブラリを公開してしまった
実際に試したところ、社内ライブラリの DTO 型を type で定義して公開した結果、利用者側でプロパティを追加できない問題が発生しました。
typescript// ライブラリ側(type で定義)
export type ApiResponse = {
status: number;
data: unknown;
};
// 利用者側(拡張したいが…)
// type ApiResponse = { ... } // 重複エラーで拡張不可
interface であれば、利用者が同名で宣言を追加するだけで拡張できます。
typescript// ライブラリ側
export interface ApiResponse {
status: number;
data: unknown;
}
// 利用者側(マージされる)
declare module "my-library" {
interface ApiResponse {
metadata?: Record<string, string>;
}
}
つまずきやすい点:宣言マージは便利ですが、意図しない拡張を許容してしまうリスクもあります。公開範囲を明確にしたうえで採用してください。
課題 2 ユニオン型を interface で表現しようとして複雑化
業務で問題になったのは、API のレスポンス状態を判別可能ユニオン(Discriminated Union)で表現しようとした場面です。
typescript// interface だけでは直接ユニオンを定義できない
interface SuccessResponse {
status: "success";
data: string;
}
interface ErrorResponse {
status: "error";
message: string;
}
// 結局 type でまとめる必要がある
type ApiResult = SuccessResponse | ErrorResponse;
最初から type で設計していれば、ユニオン型と型ガードを一貫して記述できました。
課題 3 IDE 補完が崩れて可読性が低下
型エイリアスで複雑な条件型を多用すると、IDE のホバー表示が長大になり、開発者体験が悪化しました。
typescripttype DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
// ホバー時の表示が { [K in keyof User]?: DeepPartial<User[K]> } のように展開される
単純なオブジェクト構造を interface で定義すれば、補完結果がそのまま表示されて読みやすくなります。
解決策と判断 type と interface の使い分け基準
これらの課題を踏まえ、採用した設計方針を説明します。
判断フローチャート
以下のフローで使い分けを決定します。
mermaidflowchart TD
Start["型定義が必要"] --> Q1{"外部公開 or<br/>拡張を許可?"}
Q1 -->|Yes| UseInterface["interface を採用"]
Q1 -->|No| Q2{"ユニオン・条件型<br/>が必要?"}
Q2 -->|Yes| UseType["type を採用"]
Q2 -->|No| Q3{"オブジェクト構造<br/>のみ?"}
Q3 -->|Yes| Either["どちらでも可<br/>チームルールに従う"]
Q3 -->|No| UseType
図の補足:公開 API では拡張性を優先して interface を、内部ロジックでは型演算が必要なら type を選択します。
採用した方針
検証の結果、以下の方針を採用しました。
-
公開 API・DTO は interface
- 宣言マージによる拡張を許可
- 破壊的変更のリスクを軽減
-
ユニオン型・条件型・マッピング型は type
- interface では表現できない
- 型レベルの分岐処理を記述可能
-
単純なオブジェクト型はチームルールで統一
- どちらでも書けるが、混在は避ける
- プロジェクト単位で統一ルールを設ける
採用しなかった案
「すべて type で統一する」という案も検討しましたが、以下の理由で採用しませんでした。
- ライブラリ利用者が型を拡張できなくなる
- IDE 補完が複雑化しやすい
- TypeScript 公式ドキュメントでも interface を推奨するケースがある
具体例 type と interface の実践的な使い分け
ここでは、実際のコードを使って具体的な使い分けを示します。
具体例 1 公開 API のレスポンス型(interface 推奨)
外部に公開するレスポンス型は、拡張性を考慮して interface で定義します。
typescript// api-types.ts(ライブラリ側)
export interface UserResponse {
id: number;
name: string;
email: string;
}
// 利用者が拡張可能
declare module "my-api-client" {
interface UserResponse {
avatarUrl?: string;
}
}
つまずきやすい点:
export typeで公開すると、利用者側での拡張が不可能になります。公開型は意識的に interface を選択してください。
具体例 2 判別可能ユニオン(type 推奨)
API の成功・失敗を型で区別する場合、type でユニオンを定義します。
typescript// result.ts
type Success<T> = {
ok: true;
data: T;
};
type Failure = {
ok: false;
error: Error;
};
type Result<T> = Success<T> | Failure;
// 使用例
function handleResult(result: Result<string>) {
if (result.ok) {
console.log(result.data); // string として推論
} else {
console.error(result.error.message);
}
}
ok プロパティで型が絞り込まれ、型安全な分岐処理が可能です。
具体例 3 ユーティリティ型(type 必須)
条件型やマッピング型を使うユーティリティは、type でのみ記述できます。
typescript// utils.ts
type Nullable<T> = T | null | undefined;
type RequiredKeys<T> = {
[K in keyof T]-?: undefined extends T[K] ? never : K;
}[keyof T];
type PickRequired<T> = Pick<T, RequiredKeys<T>>;
// 使用例
interface User {
id: number;
name: string;
nickname?: string;
}
type RequiredUser = PickRequired<User>;
// { id: number; name: string }
具体例 4 クラスの契約定義(interface 推奨)
クラスが実装すべきメソッドを定義する場合、interface が適しています。
typescript// contracts.ts
interface Repository<T> {
findById(id: string): Promise<T | null>;
save(entity: T): Promise<void>;
delete(id: string): Promise<void>;
}
// 実装
class UserRepository implements Repository<User> {
async findById(id: string): Promise<User | null> {
// 実装
return null;
}
async save(entity: User): Promise<void> {
// 実装
}
async delete(id: string): Promise<void> {
// 実装
}
}
つまずきやすい点:
typeでもimplementsは可能ですが、interface の方が意図が明確になります。
具体例 5 Props 型の継承(interface 推奨)
React コンポーネントの Props を継承する場合、interface の extends が読みやすくなります。
typescript// components/Button.tsx
interface ButtonProps {
label: string;
onClick: () => void;
}
interface IconButtonProps extends ButtonProps {
icon: string;
}
// type で書く場合(動作はするが冗長)
type IconButtonPropsAlt = ButtonProps & {
icon: string;
};
type と interface の詳細比較 判断基準まとめ
ここまでの内容を踏まえ、詳細な比較表と判断基準を整理します。
機能別の詳細比較
| 機能 | type | interface | 判断基準 |
|---|---|---|---|
| 宣言のマージ | 不可 | 可能 | 外部公開なら interface |
| extends による継承 | 不可(& で代用) | 可能 | 継承階層が深いなら interface |
| ユニオン型 | 可能 | 不可 | 状態管理・エラー型なら type |
| 条件型 | 可能 | 不可 | 型レベル分岐なら type |
| マッピング型 | 可能 | 不可 | ユーティリティ型なら type |
| テンプレートリテラル型 | 可能 | 不可 | 動的キー生成なら type |
| infer キーワード | 可能 | 不可 | 戻り値抽出なら type |
| IDE 補完の可読性 | 複雑化しやすい | 直感的 | シンプルな構造なら interface |
シーン別の推奨選択
| シーン | 推奨 | 理由 |
|---|---|---|
| 公開ライブラリの DTO | interface | 宣言マージで後方互換を維持 |
| REST API レスポンス型 | interface | バージョンアップ時の拡張が容易 |
| 判別可能ユニオン | type | ユニオン型は type のみ |
| Redux Action 型 | type | リテラル型のユニオンが必要 |
| ユーティリティ型 | type | 条件型・マッピング型が必須 |
| プリミティブの別名 | type | interface では不可 |
| クラスの契約定義 | interface | implements との親和性 |
| React Props(継承あり) | interface | extends が読みやすい |
| 内部関数の引数型 | どちらでも可 | チームルールに従う |
まとめ
TypeScript における type(型エイリアス)と interface(インターフェース)の使い分けは、拡張性と型演算という 2 つの軸で判断できます。
-
外部公開・拡張を許可する型は
interfaceを選択- 宣言マージにより利用者側で拡張可能
- 破壊的変更のリスクを軽減
-
ユニオン型・条件型・マッピング型は
typeを選択- interface では表現できない機能
- 型安全な分岐処理を実現
-
単純なオブジェクト型はチームルールで統一
- どちらでも書けるが、混在は避ける
ただし、これらはあくまで指針であり、プロジェクトの特性やチームの習熟度に応じて柔軟に調整してください。重要なのは、選択の根拠を明確にし、一貫性を保つことです。
関連リンク
著書
article2026年1月23日TypeScriptのtypeとinterfaceを比較・検証する 違いと使い分けの判断基準を整理
article2026年1月23日TypeScript 5.8の型推論を比較・検証する 強化点と落とし穴の回避策
article2026年1月23日TypeScript Genericsの使用例を早見表でまとめる 記法と頻出パターンを整理
article2026年1月22日TypeScriptの型システムを概要で理解する 基礎から全体像まで完全解説
article2026年1月22日ZustandとTypeScriptのユースケース ストアを型安全に設計して運用する実践
article2026年1月22日TypeScriptでよく出るエラーをトラブルシュートでまとめる 原因と解決法30選
articleshadcn/ui × TanStack Table 設計術:仮想化・列リサイズ・アクセシブルなグリッド
articleRemix のデータ境界設計:Loader・Action とクライアントコードの責務分離
articlePreact コンポーネント設計 7 原則:再レンダリング最小化の分割と型付け
articlePHP 8.3 の新機能まとめ:readonly クラス・型強化・性能改善を一気に理解
articleNotebookLM に PDF/Google ドキュメント/URL を取り込む手順と最適化
articlePlaywright 並列実行設計:shard/grep/fixtures で高速化するテストスイート設計術
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
