skip to content

Literal Types

Types whose domain is a single value — 'GET', 42, true — and the unions of them that model a closed set of options. Asked constantly because literal unions are the everyday alternative to enums and the foundation of every discriminated union you will write later.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

questions

4

In TypeScript, what is a string literal type such as 'GET', and what does the union type 'GET' | 'POST' | 'DELETE' allow as a value?

level: juniorimportance: must knowfreq 80%

answer

  1. a type with exactly one value
  2. closed set of allowed options
  3. subtype of string, not the reverse
  4. string never flows back in
  5. nothing survives compilation

basics

~20 s

A literal type's domain is a single value: the type 'GET' accepts only the string 'GET'. A union such as 'GET' | 'POST' | 'DELETE' accepts exactly those three strings and rejects every other string.

solid answer

~50 s

A string literal type is a type whose only member is that one exact string, so `let m: 'GET'` can hold `'GET'` and nothing else. Writing them in a union — `type Method = 'GET' | 'POST' | 'DELETE'` — models a closed set of options: the compiler rejects `'PUT'` and typos at the call site, and editors autocomplete the three valid values. Assignability runs one way only: every literal type is a subtype of its base primitive, so `'GET'` is assignable to `string`, but a value typed `string` is *not* assignable to `Method`, because the compiler cannot know which string it holds. All of this is compile-time only — the alias emits nothing and the values are ordinary strings at runtime, so data arriving as untyped `string` still needs a real check before it enters a `Method` position.

go deeper

for a junior

Be able to say plainly that a literal type allows exactly one value, and that a union of literals written with | is how you express a fixed set of allowed options in a parameter or field.

for a middle

Explain assignability in both directions: a literal is a subtype of its primitive, but the primitive is not assignable back to the union. Show that the union is erased and emits nothing.

for a senior

Show where the union meets untyped data — request bodies, environment variables, query strings — and how you get from string to the union with a check that actually exists at runtime rather than an assertion that pretends.

for a principal

Own the modelling call: which sets in a system deserve a closed union in a shared type surface, what adding or removing a member costs consumers, and where the boundary validation for those sets lives.

## A type with exactly one value Most type names describe a large set: `string` is every string that could ever exist, `number` every number. A **literal type** shrinks that set to a single member. The type written `'GET'` contains exactly one value — the string `GET` — and nothing else: ```ts let m: 'GET' = 'GET'; // @ts-expect-error Type '"POST"' is not assignable to type '"GET"'. m = 'POST'; ``` The syntax is deliberately confusing at first glance: `'GET'` in a type position is a *type*, while `'GET'` in a value position is a *value*. TypeScript reuses the literal's own spelling as the name of the type whose only inhabitant it is. Literal types exist for each primitive that has literal syntax: string literals (`'GET'`), numeric literals (`42`, `-1`), boolean literals (`true`, `false`) and bigint literals (`10n`). ## Unions make a closed option set On its own a single-value type is rarely useful. The everyday shape is a union — several literal types joined with `|`: ```ts type Method = 'GET' | 'POST' | 'DELETE'; function send(method: Method, url: string) { /* ... */ } send('GET', '/users'); // @ts-expect-error Argument of type '"PUT"' is not assignable to parameter of type 'Method'. send('PUT', '/users'); ``` That single alias does three jobs at once. It **documents** the contract — a reader sees the whole option set in the signature rather than the word `string` plus a hopeful comment. It **checks** callers, so a typo like `'DELET'` is a compile error at the call site instead of a 405 in production. And it **drives the editor**, which suggests the three valid values as you type the argument. The same union works anywhere a type works: as a property type (`{ method: Method }`), as a return type, or as the element type of an array (`Method[]`). ## Assignability runs one way Every literal type is a **subtype** of the primitive it belongs to. `'GET'` is a subtype of `string`, `42` is a subtype of `number`. Assignment is allowed in the widening direction and refused in the narrowing direction: ```ts const m: Method = 'GET'; const s: string = m; // fine: 'GET' is one of the strings declare const fromJson: string; // @ts-expect-error Type 'string' is not assignable to type 'Method'. const bad: Method = fromJson; ``` The refusal is the point. A value typed `string` could be any of infinitely many strings; the compiler has no basis for believing it is one of your three, so it will not let it through. This is exactly the wall you hit at the edges of a program, where values arrive from `JSON.parse`, an HTTP query parameter, or an environment variable — all of which are typed `string`. Getting from `string` to `Method` requires an actual runtime check whose result the compiler can trust, not a wish. ## The types are erased TypeScript compiles to JavaScript with the type layer removed. `type Method = 'GET' | 'POST' | 'DELETE'` emits **nothing at all** — no array of the valid values, no lookup table, no runtime validation. The values themselves are just strings; `typeof method` at runtime is `'string'`. So a literal union is a promise checked at build time about the code the compiler can see. It gives you no protection against a value that entered the process as untrusted data, and it cannot be enumerated at runtime — you cannot ask a union type "what are your members?" from JavaScript, because by then it does not exist. ## Where a literal type comes from A literal type appears either because you wrote the annotation yourself, or because the compiler inferred it from a literal expression in a position that cannot change: ```ts const a = 'GET'; // type 'GET' — the binding can never be reassigned const o = { m: 'GET' }; // type { m: string } — the property can be reassigned ``` That difference is *widening*, and it is the single most common source of surprise with literal types: the value looks identical in both lines, but only one keeps its literal type. Whether inference gives you the literal or the base primitive depends on the mutability of the place the value lands, and an explicit annotation always overrides the guess. ## Why interviews open here Literal unions are the foundation that later modelling sits on: the field that distinguishes one shape of a message from another, the finite set of statuses in a state machine, the allowed keys of a config object. An interviewer asks this early because a candidate who cannot say "the type's domain is one value, and a union of them is a closed set" will not be able to reason about anything built on top of it.

  • If a literal union is erased, how does a value that arrives as `string` from JSON ever get into a `Method` position?
    Only through a real runtime check whose result the compiler is willing to trust — comparing the value against the known members, or running it through a validator. The type layer contributes nothing at runtime, so the check must exist in emitted JavaScript. An assertion that skips the check compiles fine and silently admits `'PUT'`.
  • Does a numeric literal type behave the same way as a string literal type?
    Yes. `type Status = 200 | 404 | 500` is a closed set of three numbers with the same rules: each member is a subtype of `number`, `number` is not assignable back to the union, and the whole thing is erased. Boolean and bigint literal types work identically.
  • Why would you write `type Method = 'GET' | 'POST'` rather than just `string` with a comment listing the values?
    Because the comment is not checked. With the union, a typo at any call site is a compile error, the editor autocompletes the valid values, and renaming a member surfaces every place that used it. With `string`, all of that is on the reviewer's memory.

saying these in an interview costs you the question

  • Thinks 'GET' as a type is just a comment for readers
  • Believes a variable typed string can be passed where a literal union is expected
  • Assumes the union is validated at runtime against incoming data
  • Says the union emits an array of allowed values into the JavaScript
  • Confuses the literal type with the literal value's identity

context

open as a page

This TypeScript code fails to compile: `declare function request(url: string, method: 'GET' | 'POST'): void; const config = { method: 'GET' }; request('/users', config.method);`. What type did the compiler infer for config.method, why, and how would you fix it?

level: middleimportance: must knowfreq 68%

basics

~20 s

The compiler inferred string for config.method, because an object literal's property is a mutable location and its fresh literal type widens. Fix it by annotating the object with the union, adding as const, or passing 'GET' inline.

open as a page

In TypeScript, what does a parameter annotated `flag: true` accept, and how does the type boolean relate to the literal types true and false?

level: middleimportance: should knowfreq 45%

basics

~20 s

A parameter typed true accepts only the value true, not any truthy value. The type boolean behaves as the union true | false, so true is assignable to boolean but a value typed boolean is not assignable to true.

open as a page

In TypeScript 5.x, why does the type `'red' | 'blue' | string` behave exactly like `string`, and what is the `(string & {})` idiom that library authors write to avoid it?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

A union absorbs members that are subtypes of another member, and every string literal type is a subtype of string, so the literals are reduced away and only string remains. Writing (string & {}) instead of string blocks that reduction and keeps editor suggestions.

open as a page