Skip to content
Lesson 15 of 15

Step 1 of 7 · Reading · ~4 min

Learn

Type System Essentials

Enums and Literal Types

Many values come from a small closed set: a size, a direction, a log level. TypeScript has two ways to represent such a set: literal-type unions (the modern default) and enums (the older feature). By the end of this lesson you can build a closed set from a single array, derive its type from that array, check untrusted text against it at runtime, and explain why most new code avoids enum.

String literal unions

type Size = 'small' | 'medium' | 'large';

function label(s: Size): string {
    return `size: ${s}`;
}

label('medium');    // OK
label('huge');      // compile error: not a Size

At runtime these are ordinary strings. The union exists only for the checker and costs nothing in the output.

as const and one source of truth

By default TypeScript widens literal values to their general type:

const sizes = ['small', 'medium', 'large'];   // string[]: the literals are lost

as const keeps every literal narrow and marks the value deeply read-only to the compiler:

const SIZES = ['small', 'medium', 'large'] as const;   // readonly ['small', 'medium', 'large']
type Size = typeof SIZES[number];                       // 'small' | 'medium' | 'large'

typeof SIZES is the array's type, and indexing a type with [number] asks "what type sits at any numeric index?", which is the union of the element types. The array is now the single source of truth: add 'huge' to it and the type follows. The same trick works on objects:

const COLORS = { red: '#f00', green: '#0f0' } as const;
type ColorName = keyof typeof COLORS;                   // 'red' | 'green'

keyof T is the union of the key names of the object type T, so keyof typeof COLORS lists the keys of COLORS.

Checking untrusted text at runtime

A union is a compile-time promise. Text that arrives from input is just a string until you check it, and the check is ordinary code:

function isSize(word: string): boolean {
    return (SIZES as readonly string[]).includes(word);
}

The cast widens the read-only tuple to readonly string[] so that includes accepts any string. An array's order is also data you can use: positions can double as ranks, so a set that has a natural order, such as small to large, can express it by the order of its elements.

Numeric and boolean literal types

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

type Loading =
    | { loading: true }
    | { loading: false; data: string };

A number or boolean can be a type just like a string, and the second example is a small state machine whose loading field is the discriminant.

enum: the legacy approach

enum Color {
    Red,      // 0
    Green,    // 1
    Blue      // 2
}

enum HttpStatus { OK = 200, NotFound = 404 }
enum Direction { North = 'N', South = 'S' }    // a string enum

console.log(Color.Green);    // 1

Enums work, but they have costs:

  • Every enum compiles to a real runtime object, which becomes part of your output.
  • A numeric enum accepts any number: const c: Color = 99 compiles.
  • const enum inlines values but has caveats across module boundaries and is disabled in many setups.
  • Two identical-looking enums declared in different places are different types.

The common modern replacement is a plain object with as const:

const Direction = { North: 'N', South: 'S' } as const;
type Direction = typeof Direction[keyof typeof Direction];   // 'N' | 'S'

You get a runtime object (Direction.North) and a union type with the same name, without the quirks.

Common mistakes

  • Forgetting as const. The array widens to string[], so no union can be derived from it.
  • Treating the union as a runtime check. Text from input must still be tested with ordinary code before you rely on it.
  • Reaching for enum by reflex. A literal union is usually simpler and leaves nothing in the output.
  • Assuming as const freezes the value. It is a compile-time rule; use Object.freeze if you need a runtime guarantee.

Your exercise

Filter log lines by severity. The levels form a closed, ordered set, so one as const array can provide the type, the runtime check and the ordering. You declare it and write a function that turns level text into a rank. The starter reads the lines and leaves the filtering decision to you. The hidden tests include lines whose level is not in the set, and the minimum level itself.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…