as const vs enum: Modelling Fixed Sets of Values in TypeScript

October 7, 2026 · 3 min read

Almost every app has small fixed sets of values: order statuses, user roles, directions, themes. TypeScript gives you two main ways to model them — enum, and literal types with as const — and they behave more differently than they look.

const dirs = ['up', 'down']
const dirs2 = ['up', 'down'] as const
type Dir = (typeof dirs2)[number]
 
enum Status { Active, Inactive }
 
const Status2 = {
Active: 'active',
Inactive: 'inactive',
} as const
type Status2 = (typeof Status2)[keyof typeof Status2]
inferred
dirsstring[]

TypeScript widens literals by default: an array of "up" and "down" is just string[], because you might push "sideways" later.

0 / 3

Widening: why you need as const at all

const dirs = ['up', 'down'];   // string[]
const mode = { theme: 'dark' }; // { theme: string }

TypeScript widens literals in mutable places: an array could have more strings pushed onto it, and an object property could be reassigned, so their types are the general string.

as const tells TypeScript the value will never change:

const dirs = ['up', 'down'] as const;     // readonly ["up", "down"]
const mode = { theme: 'dark' } as const;  // { readonly theme: "dark" }

mode.theme = 'light';
// error: Cannot assign to 'theme' because it is a read-only property.

Everything becomes readonly, and every literal keeps its exact type.

One list, both a value and a type

The best trick with as const: declare the values once and derive the type from them.

const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = (typeof ROLES)[number];   // 'admin' | 'editor' | 'viewer'

function isRole(value: string): value is Role {
  return (ROLES as readonly string[]).includes(value);
}

// ROLES is a real array at runtime — map it into a <select>, validate input, etc.

Add a role to the array, and the type updates automatically. There's no second list to forget.

Enums

enum Status {
  Active,
  Inactive,
}

const s: Status = Status.Active;   // 0 at runtime

Unlike almost everything else in TypeScript, an enum generates JavaScript:

var Status;
(function (Status) {
  Status[(Status['Active'] = 0)] = 'Active';
  Status[(Status['Inactive'] = 1)] = 'Inactive';
})(Status || (Status = {}));
// { 0: 'Active', 1: 'Inactive', Active: 0, Inactive: 1 }

Numeric enums get a reverse mapping (Status[0] === 'Active') and store plain numbers — so logs, database rows and API payloads say 0 and 1. String enums avoid that:

enum Status {
  Active = 'active',
  Inactive = 'inactive',
}

…but they're nominal: a plain 'active' string isn't assignable to Status, so values from JSON or a form need converting before you can use them.

The const-object pattern

Many codebases now use a plain object with as const instead:

const Status = {
  Active: 'active',
  Inactive: 'inactive',
} as const;

type Status = (typeof Status)[keyof typeof Status];   // 'active' | 'inactive'

function setStatus(s: Status) { … }

setStatus(Status.Active);   // ✓ reads like an enum
setStatus('inactive');      // ✓ plain strings work too

You keep Status.Active-style access and autocompletion, the values are readable strings, there's no special emit, and data from an API matches the type directly.

Comparison

Numeric enumString enumas const object / union
Runtime codeYes (with reverse mapping)YesJust the object you wrote
Values at runtime0, 1, 2…'active'…'active'…
Plain string acceptedN/ANo — must use Status.XYes
Works with erasable-syntax-only toolingNoNoYes

That last row matters more each year: tools that run TypeScript by simply stripping the types (Node's built-in type stripping, some bundlers) can't handle enums, because enums aren't just types. TypeScript 5.8's erasableSyntaxOnly option flags them for this reason.

Which to use

  • Default: a union type, from an as const array or object when you also need the values at runtime.
  • Enums are fine in a codebase that already uses them consistently — prefer string enums over numeric ones.
  • Whichever you choose, define the set once and derive everything else from it.