본문으로 건너뛰기

enum과 const enum의 차이

질문

TypeScript에서 enumconst enum은 컴파일 결과와 동작 방식이 어떻게 다른가요?

답변 초안

enum은 서로 관련된 상수 값들을 하나의 이름 아래 묶어 표현하는 TypeScript 문법입니다.

매직 넘버나 매직 스트링을 의미 있는 이름으로 대체해 가독성과 타입 안정성을 높이는 것이 주된 목적입니다.

일반 enum은 컴파일 후에도 런타임 객체로 남아 JavaScript 코드를 생성하지만, const enum은 객체를 만들지 않고 사용처에 값을 그대로 인라인(inline) 합니다.

따라서 const enum은 번들 크기와 런타임 비용을 줄일 수 있지만, 런타임 객체가 없어 동작 방식이 제한적이고 isolatedModules 환경과 충돌할 수 있다는 트레이드오프가 있습니다.

한 줄 비유 일반 enum은 "전화번호부를 실제로 만들어 들고 다니는 것"이라, 런타임에 책을 펼쳐 이름↔번호를 양쪽으로 찾아볼 수 있습니다. const enum은 "컴파일러가 번호를 미리 외워서 코드에 직접 적어주는 것"이라, 번호는 바로 쓸 수 있지만 전화번호부(객체) 자체는 사라집니다.

enum이란 무엇인가

enum관련된 상수의 집합을 정의하는 문법입니다.

코드 곳곳에 흩어진 매직 값을 한 곳에 모아 이름을 부여하므로 의미가 분명해지고 오타로 인한 버그를 줄일 수 있습니다.

// enum이 없을 때 — 매직 스트링이 코드에 흩어진다
function setStatus(status: string) {}
setStatus('PENDING'); // 오타가 나도 컴파일 타임에 잡히지 않는다

// enum을 사용할 때 — 허용된 값이 타입으로 강제된다
enum Status {
Pending,
Success,
Error,
}
function setStatusSafe(status: Status) {}
setStatusSafe(Status.Pending);

enum의 종류

숫자형 enum (Numeric enum)

값을 지정하지 않으면 0부터 자동으로 증가하는 숫자가 할당됩니다.

enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}

시작 값을 직접 지정하면 그 이후 멤버가 이어서 증가합니다.

enum Direction {
Up = 1, // 1
Down, // 2
Left, // 3
Right, // 4
}

문자열 enum (String enum)

각 멤버에 문자열 값을 명시적으로 할당하는 형태입니다.

enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}

문자열 enum은 자동 증가가 없으므로 모든 멤버에 값을 직접 지정해야 합니다.

런타임에서 값 자체가 의미를 가지므로(로그, 직렬화 등) 디버깅이 쉽다는 장점이 있습니다.

이종 enum (Heterogeneous enum)

숫자와 문자열 값을 섞어 정의할 수도 있습니다.

enum Mixed {
No = 0,
Yes = 'YES',
}

다만 멤버의 값 타입이 일관되지 않아 혼란을 줄 수 있으므로 실무에서 권장되지 않습니다.

일반 enum의 컴파일 결과

일반 enum은 컴파일 후 즉시 실행 함수(IIFE)로 런타임 객체를 생성합니다.

특히 숫자형 enum은 이름 → 값뿐 아니라 값 → 이름도 조회할 수 있는 역방향 매핑(reverse mapping) 을 함께 만듭니다.

// 컴파일 전 (TypeScript)
enum Direction {
Up,
Down,
}
// 컴파일 후 (JavaScript)
var Direction;
(function (Direction) {
Direction[(Direction['Up'] = 0)] = 'Up';
Direction[(Direction['Down'] = 1)] = 'Down';
})(Direction || (Direction = {}));

// 정방향: Direction.Up === 0
// 역방향: Direction[0] === 'Up'

Direction[(Direction['Up'] = 0)] = 'Up'; 이 한 줄이 처음 보면 가장 헷갈리는데, 사실 양방향 매핑을 한 번에 만드는 코드입니다. 안쪽부터 단계별로 풀어보면 이렇습니다.

// 1단계: 안쪽 할당이 먼저 실행되어 '정방향'(이름 → 값)을 넣는다
Direction['Up'] = 0;

// 2단계: 그런데 할당식 (Direction['Up'] = 0) 자체는 '대입한 값' 0을 돌려준다
// 따라서 바깥 코드는 결국 Direction[0] = 'Up' 이 된다 ('역방향': 값 → 이름)
Direction[0] = 'Up';

즉 한 줄로 Up → 00 → 'Up'이 동시에 등록됩니다. (이 역방향은 숫자형에서만 만들어집니다.)

문자열 enum은 역방향 매핑을 생성하지 않으며, 단방향 객체만 만듭니다.

// 컴파일 전 (TypeScript)
enum Direction {
Up = 'UP',
Down = 'DOWN',
}
// 컴파일 후 (JavaScript)
var Direction;
(function (Direction) {
Direction['Up'] = 'UP';
Direction['Down'] = 'DOWN';
})(Direction || (Direction = {}));

이렇게 객체가 런타임에 남기 때문에 Object.values(Direction)로 멤버를 순회하거나 값으로부터 이름을 역조회하는 등의 동작이 가능합니다.

const enum의 컴파일 결과

const enum은 런타임 객체를 만들지 않고, 컴파일 시점에 사용처를 멤버 값으로 직접 치환(인라인) 합니다.

// 컴파일 전 (TypeScript)
const enum Direction {
Up,
Down,
}

const move = Direction.Up;
// 컴파일 후 (JavaScript)
const move = 0; /* Direction.Up */

Direction이라는 객체 선언 자체가 사라지고, Direction.Up이 있던 자리에 값 0이 그대로 박힙니다.

그 결과 enum 정의에 해당하는 코드가 번들에서 완전히 제거되어 출력 크기가 작아지고, 객체 프로퍼티 접근 비용도 사라집니다.

대신 런타임 객체가 없으므로 Object.values(Direction) 같은 객체 기반 동작이나 역방향 매핑은 사용할 수 없습니다.

주요 차이점

항목일반 enumconst enum
컴파일 결과런타임 객체 생성(IIFE)사용처에 값으로 인라인
번들 크기enum 정의 코드가 남음정의 코드가 제거되어 더 작음
런타임 비용객체 프로퍼티 접근 발생리터럴 값 접근(접근 비용 없음)
역방향 매핑(숫자형)지원미지원
런타임 순회/조회Object.values 등 가능객체가 없어 불가능
isolatedModules호환별도 설정(preserveConstEnums 등) 필요
디버깅객체로 존재해 추적이 쉬움값이 인라인되어 출처 추적이 어려움

언제 무엇을 사용하는가

enum을 사용하는 경우

  • 런타임에 멤버를 순회하거나(Object.values) 값으로부터 이름을 역조회해야 하는 경우
  • 라이브러리를 배포해 외부에서 enum 객체에 접근할 수 있어야 하는 경우
  • isolatedModules(Babel, esbuild, SWC 기반 빌드)를 사용해 단일 파일 단위로 트랜스파일하는 경우

const enum을 사용하는 경우

  • 단순히 상수 묶음으로만 쓰고 런타임 객체가 필요 없는 경우
  • 번들 크기와 런타임 비용을 최대한 줄이고 싶은 경우
  • 단, 빌드 도구가 const enum을 안전하게 처리할 수 있는지 먼저 확인해야 합니다.

enum의 대안

enum 대신 as const 객체나 union 타입을 쓰는 패턴도 널리 사용됩니다.

이 방식은 별도의 TypeScript 전용 런타임 코드를 만들지 않고 일반 JavaScript 객체로 남으며, 값과 타입을 함께 유연하게 다룰 수 있습니다.

// as const 객체 + union 타입
const Direction = {
Up: 'UP',
Down: 'DOWN',
} as const;

// 값의 union 타입을 추출
type Direction = (typeof Direction)[keyof typeof Direction]; // 'UP' | 'DOWN'

function move(direction: Direction) {}
move('UP'); // 가능

리터럴 union만 필요하다면 객체 없이 타입만 선언하는 것도 충분합니다.

type Direction = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT';

실무 주의점

  • const enumisolatedModules를 켠 환경(Babel, esbuild, SWC 등)에서 타입 정보 없이 단일 파일을 변환하지 못해 빌드 에러나 잘못된 결과를 낼 수 있으므로 주의해야 합니다.
  • 일반 숫자형 enum의 역방향 매핑은 값이 키로도 들어가므로, enum 객체를 그대로 순회하면 이름과 숫자가 모두 나와 의도치 않은 결과가 나올 수 있습니다.
  • enum 멤버 값을 외부 데이터(API 응답, DB 값)와 매핑할 때는 자동 증가하는 숫자형보다 의미가 고정되는 문자열 enum이 안전합니다.
  • 최근에는 트리 쉐이킹과 빌드 도구 호환성 측면에서 enum 대신 as const 객체나 union 타입을 선호하는 팀이 늘고 있습니다.

면접 답변

"일반 enum은 컴파일 후 런타임 객체로 남아 정방향과(숫자형의 경우) 역방향 매핑을 제공하지만, 그만큼 번들에 코드가 추가됩니다. const enum은 객체를 만들지 않고 사용처에 값을 인라인하므로 번들 크기와 런타임 비용을 줄일 수 있지만, 런타임 객체가 없어 순회가 불가능하고 isolatedModules 환경과 충돌할 수 있습니다. 런타임에 enum을 순회하거나 라이브러리로 노출해야 하면 일반 enum을, 단순 상수 묶음이면 const enum을 쓰며, 최근에는 빌드 호환성 때문에 as const 객체나 union 타입을 대안으로 쓰기도 합니다."

참고 자료

  • TypeScript Handbook - Enums
  • TypeScript Handbook - const enum / isolatedModules