본문으로 건너뛰기

교차 타입(Intersection)과 유니온 타입(Union)

질문

TypeScript에서 교차 타입(intersection type)과 유니온 타입(union type)은 무엇이고 어떻게 다른가요?

답변 초안

교차 타입은 &로 연결한 여러 타입을 모두 동시에 만족해야 하는 타입입니다.

즉 "이 값은 A이면서 동시에 B다"라는 AND 의미를 가지며, 객체 타입끼리 교차하면 양쪽의 프로퍼티를 모두 합친 타입이 만들어집니다.

유니온 타입은 |로 연결한 여러 타입 중 하나에 해당하면 되는 타입입니다.

즉 "이 값은 A이거나 B다"라는 OR 의미를 가지며, 값이 어느 쪽인지는 코드 흐름에 따라 달라집니다.

이 차이 때문에 멤버 접근 방식도 달라집니다. 교차는 모든 타입을 만족하므로 합쳐진 모든 프로퍼티에 접근할 수 있고, 유니온은 어느 쪽일지 확정되지 않아 모든 멤버가 공통으로 가진 프로퍼티만 바로 접근할 수 있습니다.

한 줄 비유 교차(&)는 "이것이면서 동시에 저것"입니다. 직원이면서 동시에 관리자인 사람처럼 양쪽 자격을 전부 가진 존재라, 양쪽 권한을 모두 쓸 수 있습니다. 유니온(|)은 "이거나 저거"입니다. 카드 결제 또는 현금 결제처럼 둘 중 무엇이 올지 모르니, 공통으로 보장되는 것만 믿고 쓸 수 있습니다.

교차 타입(Intersection Type)

교차 타입은 &로 여러 타입을 결합하며, 그 값은 결합된 모든 타입을 동시에 만족해야 합니다.

객체 타입끼리 교차하면 양쪽의 프로퍼티를 모두 가진 하나의 타입이 됩니다.

type ProductItem = {
id: number;
name: string;
type: string;
price: number;
imageUrl: string;
quantity: number;
};

// ProductItem에 할인 정보(discountAmount)를 더한 타입
type ProductItemWithDiscount = ProductItem & { discountAmount: number };

const discounted: ProductItemWithDiscount = {
id: 1,
name: "후라이드 치킨",
type: "치킨",
price: 18_000,
imageUrl: "/fried-chicken.png",
quantity: 1,
discountAmount: 2_000, // 교차로 추가된 필드
};

// ProductItem의 모든 필드 + discountAmount에 모두 접근 가능
console.log(discounted.name, discounted.discountAmount);

교차 타입은 이렇게 여러 객체 타입을 합쳐 더 구체적인 타입을 만들 때 주로 사용합니다.

type WithTimestamp = { createdAt: Date; updatedAt: Date };
type Article = { title: string; body: string };

// 게시글 타입에 공통 타임스탬프 필드를 합친다
type StoredArticle = Article & WithTimestamp;

원시 타입을 교차하면 생기는 일

교차는 "양쪽을 동시에 만족"을 의미하므로, 동시에 만족할 수 없는 원시 타입을 교차하면 never가 됩니다.

string이면서 동시에 number인 값은 존재할 수 없기 때문입니다.

type T = string & number; // 추론 결과: never

const value: T = "hello";
// Error: Type 'string' is not assignable to type 'never'.
const value2: T = 123;
// Error: Type 'number' is not assignable to type 'never'.

객체 타입에서도 같은 프로퍼티의 타입이 서로 충돌하면 그 프로퍼티가 never가 되어 사실상 값을 만들 수 없게 됩니다.

type A = { id: string };
type B = { id: number };

type C = A & B; // id: string & number → id: never

유니온 타입(Union Type)

유니온 타입은 |로 여러 타입을 나열해 정의하며, 그 값은 나열된 타입 중 하나가 됩니다.

type Id = number | string;

function printId(id: Id) {
console.log(id); // number이든 string이든 출력은 가능
}

printId(101); // 가능
printId("202"); // 가능

유니온 값은 실행 시점에 어느 멤버일지 확정되지 않으므로, 모든 멤버가 공통으로 가진 프로퍼티/연산만 타입 좁히기(narrowing) 없이 바로 접근할 수 있습니다.

// 앞서 교차 타입 예시에서 정의한 ProductItem과, 카드 아이템 타입
type CardItem = {
id: number;
name: string;
type: string;
imageUrl: string;
};

// 상품(ProductItem)이거나 카드(CardItem)인 프로모션 아이템
type PromotionEventItem = ProductItem | CardItem;

const printPromotionItem = (item: PromotionEventItem) => {
console.log(item.name); // 가능: ProductItem과 CardItem 모두 name을 가짐

console.log(item.quantity);
// Error: Property 'quantity' does not exist on type 'PromotionEventItem'.
// (CardItem에는 quantity가 없어 바로 접근 불가)
};

타입 좁히기(Narrowing)

유니온 멤버 고유의 프로퍼티를 쓰려면 먼저 어떤 타입인지 좁혀야 합니다.

원시 타입이 섞여 있으면 typeof로 구분합니다.

function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase()); // 이 블록에서 id는 string
} else {
console.log(id.toFixed(2)); // 이 블록에서 id는 number
}
}

객체 타입이라면 특정 프로퍼티의 존재 여부를 in으로 확인할 수 있습니다. 앞서 item.quantity에서 났던 에러도 이렇게 좁히면 안전하게 접근할 수 있습니다.

// 앞서 정의한 PromotionEventItem(ProductItem | CardItem)을 사용
function printQuantity(item: PromotionEventItem) {
if ("quantity" in item) {
console.log(item.quantity); // 여기서 item은 ProductItem으로 좁혀짐
} else {
console.log(item.name); // 여기서 item은 CardItem
}
}

가장 권장되는 방식은 공통 리터럴 필드(discriminant)를 두고 그 값으로 분기하는 판별 유니온(discriminated union) 입니다.

type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; side: number };
type Shape = Circle | Square;

function area(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2; // shape는 Circle로 좁혀짐
case "square":
return shape.side ** 2; // shape는 Square로 좁혀짐
}
}

교차 vs 유니온 - 핵심 차이

항목교차(&)유니온(|)
의미AND - 모든 타입을 동시에 만족OR - 멤버 중 하나
멤버 접근합쳐진 모든 프로퍼티에 접근 가능공통 프로퍼티만 바로 접근(이후 타입 좁히기 필요)
객체끼리 결합 결과양쪽 프로퍼티를 모두 합친 타입"둘 중 하나" 형태의 값을 표현
원시 타입 결합string & numbernever(불가능)string | number처럼 후보 집합
대표 용도객체 타입 확장·믹스인, 공통 필드 합치기여러 상태/케이스 표현, 판별 유니온

함께 쓰는 패턴

교차로 객체 타입 확장하기

교차 타입은 기존 객체 타입에 필드를 더해 확장하거나, 여러 타입을 믹스인처럼 합칠 때 유용합니다.

type Base = { id: string };
type Loggable = { log: () => void };

// Base에 로깅 능력을 더한 타입
type Entity = Base & Loggable;

interfaceextends와 목적은 비슷하지만, 교차 타입은 type 별칭이나 제네릭과 더 자유롭게 조합할 수 있다는 차이가 있습니다.

판별 유니온(discriminated union)

공통 리터럴 필드로 분기하는 판별 유니온은 유니온 타입을 안전하게 다루는 대표 패턴입니다.

상태(state)나 응답 형태를 표현하는 실전 예시는 별도 문서 Discriminated union으로 form state 표현하기에 정리되어 있으니 그쪽을 참고하세요.

실무 주의점

  • 원시 타입을 교차한 string & numbernever가 됩니다. 의도는 "둘 다 허용"인데 실수로 &를 쓰면 아무 값도 넣을 수 없는 타입이 만들어집니다(이때는 |가 맞습니다).
  • 교차 시 같은 이름의 프로퍼티 타입이 충돌하면 해당 프로퍼티가 never가 되어 객체 자체를 만들 수 없게 됩니다. 합치려는 타입들의 프로퍼티 이름이 겹치지 않는지 확인하세요.
  • interface extends와 교차 타입은 비슷해 보이지만, extends는 충돌 시 컴파일 에러로 즉시 알려주는 반면 교차 타입은 조용히 never로 만들어 버려 원인 파악이 늦어질 수 있습니다.
  • 유니온은 공통 프로퍼티만 바로 접근할 수 있으므로, 멤버 고유 프로퍼티를 쓰려면 typeof·in·판별 필드 등으로 반드시 타입 좁히기(narrowing)해야 합니다.
  • 유니온은 조건부 타입·매핑 타입에서 분배(distribution) 동작을 합니다. 예를 들어 T extends U ? X : Y에서 T가 유니온이면 각 멤버에 대해 따로 평가된 뒤 합쳐지므로, 의도와 다른 결과가 나올 수 있습니다.

면접 답변

"교차 타입(&)은 여러 타입을 동시에 만족해야 하는 AND 타입이고, 유니온 타입(|)은 여러 타입 중 하나에 해당하는 OR 타입입니다. 그래서 교차는 합쳐진 모든 프로퍼티에 접근할 수 있고, 유니온은 모든 멤버가 공통으로 가진 프로퍼티만 바로 접근할 수 있어 나머지는 typeof·in·판별 필드로 타입 좁히기(narrowing)해야 합니다. 교차는 객체 타입을 확장하거나 합칠 때, 유니온은 상태나 케이스를 표현하는 판별 유니온에 주로 씁니다. 주의할 점으로 string & number처럼 동시에 만족할 수 없는 원시 타입을 교차하면 never가 된다는 점이 있습니다."