Interview Q&A / TypeScript

IntermediateIn-depth4 min readUpdated 2 Oct 2026

Should you use an enum or a union of string literals in TypeScript?

SHORT ANSWER

Both describe a fixed set of allowed values. An enum creates a real JavaScript object at runtime and its members must be referenced by name. A union of string literals like "admin" | "user" exists only at compile time, adds no code, and accepts plain strings. Many teams now prefer unions, or an as const object when they also need runtime values.

Explain it simply

Think of a train ticket class: Sleeper, 3AC, 2AC, 1AC. Only these four are valid. You want the compiler to reject "4AC".

TypeScript gives two main ways to say this:

  • An enum is like an official list printed by the railways, with a name for each option. It is a real object that exists when your program runs.
  • A union of string literals is like a rule written on the booking form: "must be one of these four words". The rule is checked while you write the code and then disappears; only the plain strings remain.

Both stop typos. The difference is in what exists at runtime and how strict the compiler is about where values come from.

Example

ts
// 1. String enum
enum RoleEnum {
  Admin = "ADMIN",
  User = "USER",
}
function setRoleEnum(role: RoleEnum): void {}
setRoleEnum(RoleEnum.Admin); // OK
// setRoleEnum("ADMIN");     // Error: string is not assignable to RoleEnum

// 2. Union of string literals
type Role = "ADMIN" | "USER";
function setRole(role: Role): void {}
setRole("ADMIN");            // OK
// setRole("SUPERADMIN");    // Error

// 3. as const object: runtime values plus a union type
const ROLES = {
  Admin: "ADMIN",
  User: "USER",
} as const;
type RoleValue = (typeof ROLES)[keyof typeof ROLES]; // "ADMIN" | "USER"

setRole(ROLES.Admin);        // OK
const allRoles = Object.values(ROLES); // ("ADMIN" | "USER")[], useful for a dropdown

How it works

Enums compile to JavaScript. A string enum becomes an object like { Admin: "ADMIN", User: "USER" }. A numeric enum (enum Level { Low, High }) gets values 0, 1 and also a reverse mapping, so Level[0] is "Low". That means Object.values on a numeric enum returns both names and numbers, which surprises people.

String enums are nominal: only RoleEnum.Admin is accepted, not the string "ADMIN", even though they are equal at runtime. This is stricter, but it is awkward when values come from an API or JSON, because you have to convert them.

Literal unions are structural and erased: they add zero bytes to the output, and any matching string is accepted. They work naturally with JSON data, discriminated unions and autocomplete.

The as const object pattern gives you both: a runtime object you can loop over and a union type derived from it.

There is also const enum, which is inlined at compile time and leaves no object behind. It causes problems with tools that compile one file at a time (Babel, esbuild, the isolatedModules setting), so many projects avoid it.

Real-world use

  • Literal unions: API status fields, button variants ("primary" | "secondary"), React prop options. This is the most common choice in modern codebases.
  • as const objects: when you need the list at runtime too, such as populating a <select> or validating input.
  • Enums: existing codebases and teams that like named, nominal constants, or numeric flags that map to backend integer codes.
  • Node.js can now run TypeScript files by stripping types, and that mode does not support enums because they need code generation. TypeScript 5.8 added the erasableSyntaxOnly option to flag such syntax.

Common mistakes

  • Using numeric enums and then being confused by reverse mappings in Object.keys or Object.values.
  • Passing a raw string from an API to a function that expects a string enum, and casting with as instead of validating.
  • Thinking a literal union exists at runtime. You cannot loop over type Role; use an as const array or object if you need the values.
  • Mixing numeric and string members in one enum, which makes the code hard to reason about.

What to say in the interview

"Both restrict a value to a fixed set. An enum generates a real JavaScript object, so it has runtime presence, and string enums are nominal: you must use RoleEnum.Admin, not the plain string. Numeric enums also add a reverse mapping. A union of string literals is erased at compile time, adds no code, and accepts plain matching strings, which fits JSON and API data well. In new code I usually prefer literal unions, and if I need the values at runtime I use an as const object and derive the union type from it. I would still follow the existing convention in a codebase that uses enums."

Follow-up questions

  • What is a reverse mapping in a numeric enum?
  • What does as const do?
  • What is a const enum and why can it cause problems?
  • How would you get a list of all allowed values from a union type?

Go deeper

Read these once the basics above feel easy.

as const makes TypeScript infer the narrowest possible type: string values become literal types, objects become readonly, and arrays become readonly tuples. So const SIZES = ["S", "M", "L"] as const; lets you write type Size = (typeof SIZES)[number]; to get "S" | "M" | "L" and still use SIZES.includes(...) at runtime (with a small cast or a type guard, since includes expects the narrow type). This gives a single source of truth for both the type and the runtime list.

#enums#unions#literal-types#as-const

Related questions

Practise more TypeScript questions

Test your recall with short answers, then return to the detailed explanations when you need context.

Open TypeScript quick answers →

Get new questions like this by email

One short email when something new and useful lands. No spam, unsubscribe in one tap.

By subscribing you agree to the Privacy Policy.