Course outline · 0% complete

0/26 lessons0%

Course overview →

Literal Types, any, and unknown

lesson 2-3 · ~12 min · 6/26

Types can be exact values

A literal type is a type with exactly one allowed value. "left" (the type) accepts only "left" (the string). Alone that is not useful, but combined with | (or) it models a fixed menu of options:

let direction: "left" | "right" = "left";
direction = "right"; // fine
direction = "up";    // error: not assignable

Compare the JavaScript habit of passing magic strings and hoping nobody misspells "rigth". The | symbol builds a union type, and Unit 4 is devoted to it. For now, remember: strings and numbers themselves can be types, and a union of literals is TypeScript's built-in answer to "one of these exact values".

A variable restricted to two strings

let direction: "left" | "right" = "left";
console.log("moving " + direction);

direction = "right";
console.log("moving " + direction);

Output

moving left
moving right

The type here is the pair of quoted values themselves, so only those two exact strings are assignable. Both reassignments above are legal because each one picks a member of that pair.

Writing direction = "up" fails, and the error message lists the allowed values, which makes it self-documenting. That is the practical payoff over the plain JavaScript habit of passing magic strings: a misspelling like "rigth" is caught immediately rather than silently falling through an if chain at runtime.

any and unknown: checking off versus checking kept on

Two special types describe "could be anything", with opposite safety properties:

anyunknown
Assign anything to ityesyes
Call methods on ityes, uncheckedno, error
Type checkingswitched offstill on

any turns the compiler off for that value. The expression x.foo.bar() compiles happily and then crashes at runtime, which is exactly the class of bug you came here to escape.

unknown also accepts any value, but it keeps checking on. Every use is an error until you prove what the value is with a check such as typeof value === "string".

Values genuinely do have unknowable types when they come from outside your program. The classic source is a web API, a program on another server that yours sends a request to over the network and gets data back from, usually as JSON text. JSON.parse turns that text into a value, but nothing guarantees the other server sent the shape you expect. Type the result unknown and prove its shape before use. Unit 7 builds that full pattern.

anychecks OFFx.anything() compilescrashes at runtimeunknownchecks still ONmust prove the type firstthen use it safely
any silences the compiler entirely. unknown accepts any value but keeps every check active until you narrow it.

Narrowing an unknown before using it

This function accepts unknown and proves the value is a string before touching .length.

function describe(value: unknown): string {
  if (typeof value === "string") {
    return "a string of length " + value.length;
  }
  return "not a string";
}

console.log(describe("hello"));
console.log(describe(42));

Output

a string of length 5
not a string

Where the proof applies

  • Inside the if, TypeScript treats value as a string, so .length is allowed. Outside it, value is still unknown and .length would be an error.
  • Moving the return "a string of length " + value.length line above the if makes the compiler refuse it, because at that point nothing has established what value is. The check is not decoration, it is what earns the access.
  • Both calls compile because unknown accepts any argument. It is the use inside the function that is restricted, not the call.

flip: a function typed with a two-value union

flip(state: "on" | "off") returns the other value of the pair, and the variable it operates on carries the same union type.

function flip(state: "on" | "off"): "on" | "off" {
  if (state === "on") {
    return "off";
  }
  return "on";
}

let bulb: "on" | "off" = "off";
bulb = flip(bulb);
console.log("bulb is " + bulb);
bulb = flip(bulb);
console.log("bulb is " + bulb);

Output

bulb is on
bulb is off

Three things the types are buying here

  • The return type is the same two-value union as the parameter, so a stray return "broken" inside the function would not compile. The function cannot produce a state that callers are unprepared for.
  • Literal types are still ordinary strings at runtime. The comparison state === "on" is exactly the JavaScript comparison you already know, and nothing about the union survives into the emitted code.
  • bulb must also be annotated "on" | "off" so it can hold both values over its lifetime. Annotating it as just "off", or letting const infer that, would make the first reassignment an error.

Typing the result of JSON.parse

When your program parses text received from a web API and you do not yet know the shape, the right type for the variable is unknown.

unknown accepts the mystery value while keeping type checking on, so the compiler forces you to prove the shape before using it. That is the whole reason to prefer it here.

Two alternatives are worth ruling out explicitly:

  • any also accepts the value but disables checking, which reintroduces the silent runtime crashes TypeScript exists to prevent. It converts a compile-time question into a production incident.
  • string is wrong because JSON.parse returns the text turned into data, which could be an object, an array, a number, or a boolean. The string was the input, not the result.