skip to content

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%

answer

  1. single-value type, boolean flavour
  2. truthiness is not membership
  3. boolean has exactly two members
  4. assignable upward, never downward
  5. same rules for 200 | 404

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.

solid answer

~40 s

`flag: true` is a boolean literal type: its domain is the single value `true`, so `1`, `'yes'` or a variable typed `boolean` are all rejected — truthiness has nothing to do with it. The relationship to `boolean` is the interesting part: the checker treats `boolean` as the union `true | false`, so the two literal types are its members. Assignment therefore works upward only — `const t = true` has type `true` and flows into a `boolean`, while `let b: boolean` will not flow into a `true` parameter, because the compiler cannot know which of the two values it currently holds. Numeric literal types such as `200 | 404` behave identically against `number`. Like every literal type, both are erased: nothing checks the value at runtime.

code

typescript · 11 lines
typescript
declare function assertEnabled(flag: true): void;

const literalTrue = true;      // type: true
assertEnabled(literalTrue);    // ok

let toggle: boolean = true;    // type: boolean, i.e. true | false
// @ts-expect-error Argument of type 'boolean' is not assignable to parameter of type 'true'.
assertEnabled(toggle);

// @ts-expect-error Argument of type 'number' is not assignable to parameter of type 'true'.
assertEnabled(1);

go deeper

for a junior

Know that true and false can be used as types, that such a parameter accepts only that exact value, and that truthy values like 1 do not qualify.

for a middle

State that boolean is the union true | false and derive the one-way assignability from that. Show that a boolean annotation overrides the literal on the right-hand side.

for a senior

Demonstrate where boolean literal fields carry real modelling weight in variant object shapes, and spot the widening bug when such a value is built in an intermediate variable before being returned.

for a principal

Judge when a boolean-flag field is the right model at all versus a named literal union of states, and set the convention so shared result and config types do not accumulate ambiguous boolean parameters.

## Boolean literal types TypeScript has literal types for each primitive with literal syntax, and booleans are no exception. `true` and `false` are types as well as values: ```ts declare function assertEnabled(flag: true): void; const literalTrue = true; // type: true assertEnabled(literalTrue); // ok // @ts-expect-error Argument of type 'number' is not assignable to parameter of type 'true'. assertEnabled(1); ``` The rejection of `1` is worth dwelling on, because it is the first thing candidates get wrong. A literal type is about **identity of a value**, not about how the value behaves in a condition. `1` is truthy at runtime; it is still not the value `true`, so it is not a member of the type `true`. TypeScript's type layer never consults JavaScript's truthiness rules when checking assignability. ## boolean is the union of its two literals The checker models `boolean` as `true | false`. That is not a metaphor: the two literal types are its constituents, and everything that follows from union membership follows here. ```ts const t = true; // type: true const b: boolean = t; // ok — a member flows into the union let toggle: boolean = true; // @ts-expect-error Argument of type 'boolean' is not assignable to parameter of type 'true'. assertEnabled(toggle); ``` The second failure is the same one-way rule that governs `'GET'` and `string`, just with a two-member set instead of an infinite one. A value typed `boolean` might be `false`; the compiler has no basis for deciding it is `true`, so it refuses. And note how little the *initialiser* helps: `let toggle: boolean = true` is annotated `boolean`, and the explicit annotation is what the checker honours — the literal on the right does not sneak a narrower type past it. ## Numeric literal types work the same way ```ts type HttpOk = 200 | 201 | 204; const created: HttpOk = 201; // ok declare const anyNumber: number; // @ts-expect-error Type 'number' is not assignable to type 'HttpOk'. const bad: HttpOk = anyNumber; ``` Negative numbers are literal types too (`-1` is commonly used for a not-found sentinel), and bigint literal types exist as well (`10n`). The rules are uniform across all of them: each literal type is a subtype of its base primitive, the primitive is not assignable back, and the whole thing is erased. ## Why anyone annotates something as `true` A lone `flag: true` parameter is rare and usually a smell — if only one value is legal, the parameter probably should not exist. Boolean literal types earn their keep as **fields inside object shapes**, where two variants of a record differ by a single flag: ```ts type Result = | { ok: true; data: string } | { ok: false; error: string }; ``` Here `ok: true` and `ok: false` are not decoration: they are what makes the two members of this union distinguishable to the compiler, because no single value can satisfy both. Without literal types on that field — if it were `ok: boolean` on both sides — the two shapes would be far less distinguishable and the modelling would collapse. A second common home is a configuration type where one option only makes sense in one state, and a third is a return type such as `boolean` narrowed to `true` on a helper that can only succeed. ## Widening applies here too A fresh boolean literal widens exactly like a string literal: ```ts const a = true; // true let c = true; // boolean const d = { ok: true }; // { ok: boolean } ``` The third line catches people out when they build a `Result` value in a variable before returning it: the property widened to `boolean`, and the object no longer matches the `{ ok: true; data: string }` member. The fixes are the same as for string literals — annotate the variable with the target type, or use `as const`. ## And it is all gone at runtime `assertEnabled(toggle)` failing to compile does not mean anything is checked when the program runs; a value that reached the function through `any`, an assertion, or unvalidated JSON will happily be `false`. The literal type expresses a contract the compiler enforces over the code it can see, nothing more. ## What the interviewer is checking Two things, and they are quick to hear. First, whether you separate *value identity* from *truthiness* — a candidate who says `true` accepts "anything truthy" has not internalised what a literal type is. Second, whether you can state that `boolean` is the union `true | false`, which is the fact that makes the one-way assignability obvious rather than arbitrary.

  • Why does `let toggle: boolean = true; assertEnabled(toggle);` fail even though the initialiser is literally `true`?
    Because the explicit annotation wins: `toggle` is declared `boolean`, so its type is `true | false` regardless of what it was initialised with, and it stays that way through later assignments. The compiler cannot promise it holds `true` at the call, so it refuses. Drop the annotation, or use `const`, and the binding keeps the type `true`.
  • What type does the property get in `const r = { ok: true, data: 'x' };`, and why does that matter?
    `{ ok: boolean; data: string }` — the fresh literal widens because the property is mutable. That is why building such a value in a variable can stop it matching an `{ ok: true; data: string }` member of a union. Annotate the variable with the target type, or add `as const`, to keep `ok` at `true`.
  • Do numeric literal types support anything a string literal type does not?
    They follow identical rules — subtype of `number`, no assignment back from `number`, fully erased. Negative literals such as `-1` are valid literal types, and bigint has its own literal types (`10n`). The only real difference is ergonomic: numeric unions like `200 | 404` are common in status modelling.

saying these in an interview costs you the question

  • Says a parameter typed true accepts any truthy value
  • Thinks boolean and true are interchangeable in both directions
  • Believes an annotation of boolean still keeps the literal type
  • Expects the compiler to check the flag at runtime
  • Assumes numeric literal types cannot be negative

context