A literal type narrows a primitive to one specific value rather than the whole category — "loading" as a type, not just string.
let status: "loading" | "success" | "error";
status = "loading"; // fine
status = "done"; // Error: Type '"done"' is not assignable
Plain variable declarations widen literals back to their general type by default, which is why as const matters:
let a = "hello"; // inferred as string (widened)
const b = "hello"; // inferred as "hello" (a const primitive can't change, so TS keeps it literal)
const config = { role: "admin" };
// config.role is inferred as string, NOT "admin" — object properties widen even with const,
// because the property itself could still be reassigned (config.role = "guest" is legal)
const config2 = { role: "admin" } as const;
// config2.role is now the literal type "admin", and config2 itself is deeply readonly
as const is the practical fix any time you want an object or array literal to keep its exact literal types instead of widening — extremely common when defining a fixed set of options, a Redux-style action, or an array meant to be treated as a tuple.
const point = [3, 4] as const; // type: readonly [3, 4] — a tuple, not number[]