1. 개요

interface User {
  name: string
}
 
type User2 = {
  name: string
}

이 둘은 거의 같은 걸 선언한다. 그래서 “아무거나 써도 된다” 는 말을 자주 듣는데, 완전히 같지는 않다. 프로젝트 안에서 기준 없이 섞어 쓰면 나중에 “왜 이건 interface 고 저건 type 이지” 하는 리뷰 코멘트가 반복된다. 이번 편은 실제로 다른 지점 위주로 정리한다.


2. 핵심 내용

2-1. 겹치는 부분: 객체 모양 선언

객체 타입을 선언하는 용도로는 둘이 거의 동일하다. 확장도 둘 다 된다.

interface Animal {
  name: string
}
interface Dog extends Animal {
  breed: string
}
 
type Animal2 = {
  name: string
}
type Dog2 = Animal2 & {
  breed: string
}

interface 는 extends 로, type 은 & 로 확장한다는 문법 차이만 있을 뿐 결과는 같다. 다만 이 extends는 컴파일하면 그냥 사라지는 타입 레벨 표시일 뿐이다. 자바스크립트의 class extends는 실제로 런타임에 프로토타입 체인을 연결하는 것과 다르다 — 그 동작은 자바스크립트 기초 4 - 프로토타입 체인과 class에서 다뤘다.

2-2. type 만 되는 것: 유니온, 원시 타입 별칭, 튜플

interface 는 객체(와 함수, 클래스) 모양만 선언할 수 있다. 유니온이나 원시 타입에는 못 쓴다.

type Status = "pending" | "approved" | "rejected" // interface 로는 불가능
type Id = string | number
type Coord = [number, number] // 튜플
 
interface Status {} // 이런 식으로 유니온을 표현할 방법이 없다

2편에서 본 태그된 유니온, 이런 원시 타입 조합, 함수 타입 별칭까지 전부 type 의 영역이다. interface 로는 아예 표현이 안 되니 이건 취향 문제가 아니라 기능 차이다.

2-3. interface 만 되는 것: 선언 병합

같은 이름으로 interface 를 두 번 선언하면 자동으로 합쳐진다.

interface Window {
  myGlobalFlag: boolean
}
 
interface Window {
  anotherFlag: string
}
 
// 최종적으로 Window 는 두 필드를 모두 가진 것으로 합쳐진다
declare const w: Window
w.myGlobalFlag
w.anotherFlag

type 으로 같은 걸 시도하면 에러가 난다.

type Config = { a: string }
type Config = { b: string } // Error: 중복 식별자 'Config'

선언 병합은 보통 직접 쓸 일은 적지만, 라이브러리 타입 정의를 프로젝트에서 확장할 때(예: Express 의 Request 에 커스텀 필드를 추가하는 declare global 패턴, 또는 .d.ts 에서 전역 타입을 보강할 때) 필수적으로 쓰인다. 이게 필요하다면 반드시 interface 여야 한다.

2-4. 에러 메시지의 가독성

사소하지만 실무에 영향을 주는 차이. interface 로 선언한 타입은 에러 메시지에 이름이 그대로 나온다. 복잡하게 중첩된 type 은 펼쳐진 형태로 나올 때가 있어 읽기 불편할 수 있다. 이 차이는 TypeScript 버전과 상황에 따라 편차가 있어서 절대적인 기준은 아니지만, 복잡한 객체 타입일수록 interface 쪽이 에러를 좀 더 깔끔하게 보여주는 경향이 있다.

2-5. 실질적 기준

기능 차이를 정리하면 선택 기준은 이렇게 된다.

  • 유니온, 튜플, 원시 타입 별칭, 함수 타입, 매핑된 타입이 필요하면 무조건 type. 선택지가 없다.
  • 공개 API 로 노출되는 객체 모양(라이브러리의 옵션 타입, 다른 팀이 확장할 수도 있는 타입)이면 interface. 선언 병합으로 나중에 필드를 보강할 여지를 남긴다.
  • 둘 다 되는 평범한 객체 모양(컴포넌트 props, 일반 DTO)이라면 팀 컨벤션을 따르면 된다. TypeScript 공식 핸드북도 “가능하면 interface 를 쓰고, 유니온이 필요하면 type 을 쓰라” 는 정도의 느슨한 권고만 한다.

중요한 건 “뭐가 더 우월한가” 가 아니라 유니온이 필요한 순간 interface 로는 막힌다는 것을 미리 알고 설계하는 것이다. 처음에 interface 로 시작했다가 나중에 “이 타입, 다른 타입이랑 유니온으로 묶어야 하네” 싶어서 type 으로 바꾸는 리팩터링은 흔하다.


3. 마무리

요약

  • 객체 모양 선언은 interface 와 type 이 거의 동일하다. 확장 문법만 extends 대 & 로 다르다.
  • 유니온·튜플·원시 타입 별칭·함수 타입은 type 만 가능하다. interface 로는 표현할 수 없다.
  • 같은 이름으로 여러 번 선언해 자동으로 합쳐지는 선언 병합은 interface 만 가능하다. 전역 타입 보강에 쓴다.
  • 공개 API 모양은 interface, 유니온이 얽히면 type. 나머지는 팀 컨벤션을 따르면 된다.

다음은 6편이다. unknown 과 any 의 차이를 다룬다.