TypeScript's Built-in Types: A Complete Reference

Learn every major built-in TypeScript type — the primitives, the special top/bottom types (any, unknown, never, void), and object/array/function types — and understand exactly when each one should and shouldn't be used.

TypeScript's type system is built from a fairly small set of built-in types that combine to describe almost any JavaScript value; knowing what each one means, and specifically the difference between the dangerous any and the safe unknown, is essential before moving on to interfaces and generics.

The Primitive Types

string, number, and boolean are the three everyday primitives, mirroring JavaScript's own typeof categories, and they were covered in the JavaScript foundations lesson. TypeScript also gives explicit types to two special JavaScript values: null (an intentional absence of a value) and undefined (a value that hasn't been assigned yet) — under strict mode, these are their own distinct types and are not automatically assignable to string, number, etc., which forces you to handle the 'this might be missing' case explicitly.

bigint types arbitrary-precision integers written with an n suffix (10n), used when a number might exceed JavaScript's safe integer range (2^53 - 1) — this is rare in everyday web code but important for things like cryptography or exact large-integer math. symbol types the Symbol primitive, a guaranteed-unique value most often used as a special, collision-free object property key — you'll see it far more rarely than the other primitives, mostly in advanced or library code.

• let username: string = "ada"; • let age: number = 30; • let isActive: boolean = true; • let missing: null = null; • let notSet: undefined = undefined; • let big: bigint = 9007199254740993n; • let key: symbol = Symbol("id");

The Top and Bottom Types: any, unknown, never, and void

any opts a value entirely out of type checking — TypeScript will let you do absolutely anything with an any value (call it, access any property, pass it anywhere) with zero safety, and that unsafety silently spreads to anything the any value touches. It exists mainly as an escape hatch for gradually migrating JavaScript code, and using it deliberately in new TypeScript code is almost always a mistake worth avoiding.

unknown is the type-safe alternative to any: it also represents 'a value of any type,' but TypeScript refuses to let you do anything with an unknown value until you've narrowed it down (with a typeof check, an instanceof check, or similar) to a more specific type. This makes unknown the correct choice whenever you genuinely don't know a value's type ahead of time — for example, the result of JSON.parse or data from an external API — because it forces you to validate before use instead of trusting blindly.

void is the return type of a function that doesn't return a meaningful value (most commonly used for callbacks and event handlers that just perform an action). never is the type of a value that can genuinely never occur — a function that always throws an error, or one with an infinite loop, returns never, because it never actually completes and hands back control. never is also extremely useful for 'exhaustiveness checking': if you handle every case of a union type in a switch, the type left over in the default branch should be never, and if it isn't, that's a compile-time signal you missed a case.

• any = opts out of type checking entirely — avoid in new code. • unknown = type-safe 'could be anything' — must narrow before use. • void = a function that returns nothing meaningful. • never = a function/value that can never successfully produce a result (always throws, infinite loop, or exhaustiveness leftover).

Object Types, Arrays, and Functions

Beyond primitives, TypeScript can describe the *shape* of objects directly inline: { name: string; age: number } is itself a valid type, matching any object with exactly (at least) those two properties with those types. This inline shape syntax is the foundation that interface and type (covered in a later lesson) build on top of. Arrays are typed with a trailing [], like number[] for an array of numbers or string[] for an array of strings (arrays and the more specialized tuple type get their own dedicated lesson later in this course).

Function types describe both the parameter types and the return type: (a: number, b: number) => number is the type of a function taking two numbers and returning a number. This lets you type not just functions themselves, but variables and parameters that are expected to *hold* a function, such as a callback parameter or an event handler prop in React.

• let user: { name: string; age: number } = { name: "Ada", age: 30 }; • let scores: number[] = [10, 20, 30]; • let onClick: (event: string) => void = (event) => console.log(event); • function add(a: number, b: number): number { return a + b; }

All the built-in types in one place

any disables checking entirely; unknown requires you to prove the type before using the value. void means 'nothing useful returned'; never means 'this can't return at all'.

let username: string = "ada";
let age: number = 30;
let isActive: boolean = true;
let missing: null = null;
let notSet: undefined = undefined;
let big: bigint = 100n;
let id: symbol = Symbol("user-id");

let anything: any = "start as a string";
anything = 42; // allowed, but unsafe — no checking at all

let incoming: unknown = JSON.parse("42");
// incoming.toFixed(2); // Error: must narrow first
if (typeof incoming === "number") {
  incoming.toFixed(2); // OK, narrowed to number
}

function logMessage(msg: string): void {
  console.log(msg); // returns nothing meaningful
}

function fail(reason: string): never {
  throw new Error(reason); // never completes normally
}

Code Examples

processAny compiles without complaint even though calling .toUpperCase() on a number will throw at runtime — any gave up all checking. processUnknown forces a typeof check before it will let you call a string method, so the exact same kind of mistake is caught and handled safely instead of crashing. Expected result: processAny(42) throws a TypeError at runtime; processUnknown(42) safely returns "not a string".

if (typeof data === "string") { ... } — this narrowing check is required before TypeScript will treat data as a string.

Because shape can only ever be "circle" or "square", TypeScript narrows it to never inside the default branch once both real cases are handled. If someone later adds a third shape to the Shape union without adding a matching case, this line stops compiling — turning a missed case into an immediate, compile-time error instead of a silent runtime bug. This pattern is one of the most practical uses of never in everyday TypeScript code.

function processAny(data: any) {
  return data.toUpperCase(); // compiles, but crashes at runtime if data isn't a string
}

function processUnknown(data: unknown) {
  if (typeof data === "string") {
    return data.toUpperCase(); // safe — narrowed inside the if block
  }
  return "not a string";
}

processAny(42);      // compiles, throws at runtime: 42.toUpperCase is not a function
processUnknown(42);  // compiles AND runs safely: returns "not a string"
type Shape = "circle" | "square";

function area(shape: Shape, size: number): number {
  switch (shape) {
    case "circle":
      return Math.PI * size * size;
    case "square":
      return size * size;
    default:
      const exhaustiveCheck: never = shape; // errors if a case was missed
      return exhaustiveCheck;
  }
}

Important Rules

Avoid any in new code — it silently disables the type checker for that value and everything it touches.

Prefer unknown over any whenever a value's type genuinely isn't known ahead of time (JSON parsing, external API responses).

Use void for callbacks/handlers that don't return anything meaningful, and never for functions that always throw or loop forever.

Reach for never in switch/default branches to get compile-time exhaustiveness checking on union types.

Where this shows up

Parsing JSON from an API response, reading environment variables, and handling user input are the three most common places unknown (instead of any) earns its keep in production TypeScript — anywhere data enters your program from outside its own type-checked boundaries, unknown plus narrowing is the safe default.

function handleResponse(data: any) { return data.items.map((i: any) => i.name); } function handleResponse(data: unknown) { if (isValidResponse(data)) { return data.items.map(i => i.name); } throw new Error("Invalid response shape"); }

Typing incoming, unverified data (like an API response) as any throws away all safety exactly where you need it most. unknown plus an explicit validation step (a type guard function) forces you to confirm the shape before using it.

[multiple-choice] Why is unknown generally preferred over any for data of an uncertain type? A. unknown is faster at runtime B. unknown requires narrowing before you can use the value, preserving type safety; any disables checking entirely C. unknown and any are functionally identical D. unknown can only hold objects, not primitives

B. unknown requires narrowing before you can use the value, preserving type safety; any disables checking entirely — unknown forces you to prove a value's type (via typeof, instanceof, etc.) before using it, while any lets you do anything with no checking at all, which can hide real bugs.

[multiple-choice] Which return type indicates a function that never successfully completes, such as one that always throws? A. void B. undefined C. never D. any

C. never — never describes a value or function result that can genuinely never occur — a function that always throws or loops forever returns never, since it never actually hands back control normally.

[predict-output] Given function log(msg: string): void { console.log(msg); }, what does void describe here? A. The function must return the string undefined B. The function's return value isn't meant to be used C. The function will throw an error D. The function returns a number

B. The function's return value isn't meant to be used — void marks a function whose return value carries no meaningful information and isn't intended to be used by the caller — common for callbacks and side-effect-only functions.

Write a function describeValue(value: unknown): string that uses typeof checks to return "a string: <value>" for strings, "a number: <value>" for numbers, and "something else" for anything else — without using any anywhere.

function describeValue(value: unknown): string { if (typeof value === "string") { return a string: ${value}; } if (typeof value === "number") { return a number: ${value}; } return "something else"; }

Use typeof value === "string" and typeof value === "number" as narrowing checks before using string/number-specific behavior.