In TypeScript, what is a string literal type such as 'GET', and what does the union type 'GET' | 'POST' | 'DELETE' allow as a value?
answer
- a type with exactly one value
- closed set of allowed options
- subtype of string, not the reverse
- string never flows back in
- nothing survives compilation
basics
~20 sA 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 sA 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
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.
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.
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.
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