T-CREATOR

<div />

TypeScriptのtypeとinterfaceを比較・検証する 違いと使い分けの判断基準を整理

2026年1月23日
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 つに分かれます。

  1. 公開 API・ライブラリとしての拡張性

    • 利用者が型を拡張できるか
    • 破壊的変更を避けられるか
  2. 内部ロジックでの型安全と表現力

    • ユニオン型や条件型を使えるか
    • 型レベルで分岐処理を記述できるか

この 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 を選択します。

採用した方針

検証の結果、以下の方針を採用しました。

  1. 公開 API・DTO は interface

    • 宣言マージによる拡張を許可
    • 破壊的変更のリスクを軽減
  2. ユニオン型・条件型・マッピング型は type

    • interface では表現できない
    • 型レベルの分岐処理を記述可能
  3. 単純なオブジェクト型はチームルールで統一

    • どちらでも書けるが、混在は避ける
    • プロジェクト単位で統一ルールを設ける

採用しなかった案

「すべて 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 の詳細比較 判断基準まとめ

ここまでの内容を踏まえ、詳細な比較表と判断基準を整理します。

機能別の詳細比較

機能typeinterface判断基準
宣言のマージ不可可能外部公開なら interface
extends による継承不可(& で代用)可能継承階層が深いなら interface
ユニオン型可能不可状態管理・エラー型なら type
条件型可能不可型レベル分岐なら type
マッピング型可能不可ユーティリティ型なら type
テンプレートリテラル型可能不可動的キー生成なら type
infer キーワード可能不可戻り値抽出なら type
IDE 補完の可読性複雑化しやすい直感的シンプルな構造なら interface

シーン別の推奨選択

シーン推奨理由
公開ライブラリの DTOinterface宣言マージで後方互換を維持
REST API レスポンス型interfaceバージョンアップ時の拡張が容易
判別可能ユニオンtypeユニオン型は type のみ
Redux Action 型typeリテラル型のユニオンが必要
ユーティリティ型type条件型・マッピング型が必須
プリミティブの別名typeinterface では不可
クラスの契約定義interfaceimplements との親和性
React Props(継承あり)interfaceextends が読みやすい
内部関数の引数型どちらでも可チームルールに従う

まとめ

TypeScript における type(型エイリアス)と interface(インターフェース)の使い分けは、拡張性型演算という 2 つの軸で判断できます。

  • 外部公開・拡張を許可する型interface を選択

    • 宣言マージにより利用者側で拡張可能
    • 破壊的変更のリスクを軽減
  • ユニオン型・条件型・マッピング型type を選択

    • interface では表現できない機能
    • 型安全な分岐処理を実現
  • 単純なオブジェクト型はチームルールで統一

    • どちらでも書けるが、混在は避ける

ただし、これらはあくまで指針であり、プロジェクトの特性やチームの習熟度に応じて柔軟に調整してください。重要なのは、選択の根拠を明確にし、一貫性を保つことです。

関連リンク

著書

とあるクリエイター

フロントエンドエンジニア Next.js / React / TypeScript / Node.js / Docker / AI Coding

;