
최근까지도 나는 type 과 interface를 크게 구분하지 않았다.
상수나 유니온은 type으로, API 응답 객체는 interface로 썼다.
그러다 코드리뷰에서 type 대신 interface를 써보라는 제안을 받았다.
그때 문득, 나는 여기서 type과 interface를 왜 구분해서 쓰는지 명확하게 설명하지 못하고 있다는 걸 깨달았다.
type과 interface 구분 질문은 면접에서도 자주 등장한다 — "차이가 무엇인가요?", "어느 쪽을 더 선호하시나요?" 같은 식으로.
답변용 단순 외우기나, 팀 관습을 따라하는 것이 아닌, 내가 선택한 방식에 대해 설명할 수 있는 기준이 필요했다.
그래서 TypeScript 공식 문서를 훑어봤다.
TypeScript 공식 문서는 둘의 관계를 이렇게 정리한다.
Type aliases and interfaces are very similar, and in many cases you can choose between them freely.
type alias와 interface는 매우 비슷해서, 대부분의 경우 둘 중 무엇을 쓸지 자유롭게 선택할 수 있다.
Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.
interface의 거의 모든 기능은 type에서도 사용할 수 있다. 다만 핵심적인 차이는, type은 한 번 만들고 나면 다시 열어 새 속성을 추가할 수 없는 반면 interface는 언제든 확장 가능하다는 점이다.
공식 문서: TypeScript Handbook - Differences Between Type Aliases and Interfaces
공식 문서에서 강조하는 핵심은 “다시 열 수 있는가”였다.
interface는 확장 가능한 객체 구조에 적합하고, type은 union, tuple, conditional type처럼 타입 표현 자체를 조합할 때 더 적합하다.
type Status = "idle" | "loading";
type Point = [number, number];
type Nullable<T> = T | null;
처음에는 extends와 intersection(&)의 차이를 크게 의식하지 않았다.
객체 타입을 합칠 때도 둘을 비슷한 방식으로 생각하고 사용했다.
type A = {
id: string;
};
type B = {
id: number;
};
type C = A & B;
선언 자체는 문제없이 통과한다.
하지만 id 프로퍼티가 서로 다른 타입(string / number)으로 선언되어 있기 때문에, intersection 과정에서 string & number로 계산된다.
type C = {
id: never; // string & number
};
동시에 string이면서 number인 값은 존재할 수 없기 때문에, 결과적으로 never가 되어버린다.
즉, 같은 프로퍼티를 서로 다른 타입으로 정의했더라도 intersection은 선언 단계에서 바로 오류를 내지 않는다.
반면 interface extends는 이런 상황을 선언 단계에서 바로 드러낸다.
interface A {
id: string;
}
interface B extends A {
id: number;
}
// Interface 'B' incorrectly extends interface 'A'.
// Types of property 'id' are incompatible.
공식 문서: TypeScript Handbook - Interface Extension vs. Intersection
The principal difference between the two is how conflicts are handled, and that difference is typically one of the main reasons why you’d pick one over the other between an interface and a type alias of an intersection type.
둘 사이의 핵심적인 차이는 충돌을 어떻게 처리하느냐이며, 이 차이는 interface와 intersection type alias 중 하나를 선택하는 주요 이유 중 하나다.
이 차이를 이해하고 나서부터는 객체 contract나 계층 구조에서는 interface extends를 더 선호하게 됐다.
서로 다른 타입 정의를 선언 단계에서 바로 발견할 수 있었기 때문이다.
나는 구분하기 시작한 이후로 이렇게 사용하고 있다.
| 구분 | 주로 사용하는 방식 | 이유 |
|---|---|---|
| 객체 contract / API 응답 / props | interface | 객체 구조를 명확하게 표현하기 좋음 |
| 객체 계층 확장 | interface extends | 선언 단계에서 타입 충돌을 바로 확인 가능 |
| 복잡한 객체 타입을 재사용해야 할 때 | interface | 에러 메시지나 hover에서 타입 이름이 비교적 일관되게 표시되어 IDE에서 읽기 좋음 |
| union / tuple / conditional type | type | 타입 표현 자체를 조합하는 데 유연함 |
| primitive alias | type | 원시 타입에 의미 있는 이름을 부여하기 쉬움 |
물론 프로젝트마다 컨벤션은 다르다.
하지만 최소한 지금은 "그냥 익숙해서"가 아니라, 왜 이 상황에서 interface를 선택했고, 왜 여기서는 type이 더 적절한지는 설명할 수 있게 됐다.
좋은글 잘 읽었습니다.
한 가지 더 보태자면, TS팀이 객체 타입에 interface를 권하는 이유 중에 컴파일 성능, 에러 메시지(네임드 타입이라 캐싱·호버가 유리)도 있어요! 큰 코드베이스에선 이게 의외로 체감되는 것 같다는 생각이 듭니다!!