TypeScript Basic Types · Lesson 4 of 17
any, unknown, never, Literal Types and Enums
A few special types do a lot of work in real code. unknown safely represents "I don't know yet" (unlike the dangerous any), never marks impossible cases, and literal types let a variable hold only specific values like "pending" | "shipped". Knowing when to use each – and when to avoid enums – is a mark of an experienced TypeScript developer.
In this lesson
anyvsunknownvoidandnever- Literal types and
as const - Enums vs union types
any vs unknown
const fromApi: any = JSON.parse('{"name":"Asha"}');
console.log(fromApi.name.toUpperCase()); // compiles: any switches checking off
// fromApi.age.toFixed(2) would also compile – and crash at run time
const safe: unknown = JSON.parse('{"name":"Ravi","age":21}');
// safe.name; // error: 'safe' is of type 'unknown'
if (typeof safe === "object" && safe !== null && "name" in safe && typeof safe.name === "string") {
console.log(safe.name.toUpperCase()); // allowed after checking
}
ASHA RAVI
any | unknown | |
|---|---|---|
| Accepts any value | yes | yes |
| Lets you use it freely | yes – no checks at all | no – you must narrow it first |
| Spreads to other code | yes, silently | no |
| Use for | emergencies while migrating old JS | API responses, JSON, user input, catch errors |
any turns TypeScript off
Every any is a hole in your type safety, and it spreads: values derived from it are any too. Prefer unknown and narrow it, and enable ESLint's no-explicit-any rule in team projects.
void and never
function logVisit(page: string): void { // returns nothing useful
console.log(`visited ${page}`);
}
function fail(message: string): never { // never returns normally
throw new Error(message);
}
function parsePercent(text: string): number {
const n = Number(text);
if (Number.isNaN(n) || n < 0 || n > 100) fail(`invalid percent: ${text}`);
return n; // TypeScript knows fail() didn't return
}
logVisit("/learning/typescript");
console.log(parsePercent("87"));
try {
parsePercent("120");
} catch (e) {
console.log(e instanceof Error ? e.message : e);
}
visited /learning/typescript 87 invalid percent: 120
void means "returns nothing you should use". never means "this can never happen" – a function that always throws or loops forever, or a variable in a branch that's impossible. Chapter 5 uses never for exhaustiveness checks.
Literal types and as const
type Status = "pending" | "shipped" | "delivered"; // only these three strings
type Rating = 1 | 2 | 3 | 4 | 5;
let status: Status = "pending";
status = "shipped";
// status = "lost"; // error: not assignable to type 'Status'
function rate(stars: Rating): string { return "★".repeat(stars); }
console.log(status, rate(4));
const config = { env: "production", retries: 3 } as const; // deeply readonly, literal types
// config.retries = 5; // error: retries is read-only
const roles = ["admin", "editor", "viewer"] as const;
type Role = (typeof roles)[number]; // "admin" | "editor" | "viewer"
const r: Role = "editor";
console.log(config.env, roles.length, r);
shipped ★★★★ production 3 editor
as const freezes the types of a literal: strings stay literal ("production", not string) and everything becomes readonly. Deriving Role from the roles array keeps the list and the type in sync automatically.
Enums vs union types
enum Direction { Up, Down, Left, Right } // numeric enum: 0, 1, 2, 3
enum LogLevel { Info = "INFO", Warn = "WARN", Error = "ERROR" } // string enum
console.log(Direction.Left, Direction[2], LogLevel.Warn);
type Level = "INFO" | "WARN" | "ERROR"; // the union-type alternative
function log(level: Level, msg: string): void { console.log(`[${level}] ${msg}`); }
log("WARN", "disk 90% full");
log(LogLevel.Error, "service down"); // a string enum value is assignable
2 Left WARN [WARN] disk 90% full [ERROR] service down
enum | Union of literals | |
|---|---|---|
| Exists at run time | yes – generates a JavaScript object | no – erased completely |
| Works with type stripping (Node, esbuild isolated files) | no – needs a real compiler | yes |
| Values in calls | LogLevel.Warn | "WARN" |
| Iterate over values | yes (with care) | use an as const array |
Modern recommendation
Many teams now prefer union types (plus an as const array when they need the list at run time) over enums: they're simpler, have no runtime code and work with Node's built-in type stripping. Enums are still common in Angular codebases – know both.
Practice
Try it yourself
Define
const SIZES = ["S", "M", "L", "XL"] as const, derive aSizetype, and writeisSize(value: unknown): value is Size.Show solution
const SIZES = ["S", "M", "L", "XL"] as const; type Size = (typeof SIZES)[number]; function isSize(value: unknown): value is Size { return typeof value === "string" && (SIZES as readonly string[]).includes(value); } for (const v of ["M", "XXL", 42]) console.log(v, isSize(v));M true XXL false 42 false
Quick check
Why prefer unknown over any?
unknown forces you to check the value before using it; any disables all checking.
What does a function returning never do?
It never returns normally – it always throws or never ends.
What does as const do to an object literal?
Makes it deeply readonly and keeps literal types for its values.
Summary
- Avoid any; use unknown for untrusted data and narrow it.
- void = no useful return; never = impossible or never returns.
- Literal unions and as const model fixed sets of values; prefer them to enums in modern code.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.