TypeScript's type system includes primitives (string, number, boolean), arrays (number[] or Array<number>), tuples for fixed-length arrays, literal types for exact values, and special types — any opts out of checking, unknown is the type-safe alternative, never represents impossible values, and void marks functions that don't return.
string, number, boolean, bigint, symbol, null, undefined — with strictNullChecks, null and undefined are separate types that must be explicitly included.
Exact value types like 'hello' or 42. const infers literals, let infers widened types. as const creates deep readonly literal types.
any opts out of type checking entirely. unknown is the type-safe alternative — holds anything but requires narrowing before use.
never represents impossible values (throwing functions, exhausted unions). void is for functions that don't return meaningful values.
Arrays are variable-length (number[]). Tuples are fixed-length with per-position types ([string, number]). readonly variants prevent mutations.
TypeScript's basic types form the foundation of the type system. Every more complex type (generics, mapped types, conditional types) builds on these primitives.
TypeScript has the same primitive types as JavaScript:
string — text values: let name: string = 'Alice';number — all numbers (integer and float): let age: number = 30;boolean — true or false: let active: boolean = true;bigint — arbitrary-precision integers: let big: bigint = 100n;symbol — unique identifiers: let id: symbol = Symbol('id');null and undefined — their own types when strictNullChecks is enabledWith strictNullChecks, null and undefined are separate types that must be explicitly included: string | null. Without it, every type implicitly includes null/undefined — a major source of bugs.
Literal types narrow a type to an exact value:
type Direction = 'north' | 'south' | 'east' | 'west';
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = true | false;const declarations infer literal types: const x = 'hello' types x as 'hello', not string. let declarations infer the widened type: let x = 'hello' types x as string.
as const creates deep readonly literal types from any value: const config = { port: 3000 } as const types port as 3000 (not number).
Two equivalent syntaxes:
number[] — shorthand, most commonArray<number> — generic syntax, useful for complex element typesReadonly arrays prevent mutations: readonly number[] or ReadonlyArray<number>. Methods like push, pop, and splice are removed from the type.
Tuples are fixed-length arrays where each position has a specific type:
type Point = [number, number];
type NameAge = [string, number];
type Rest = [string, ...number[]]; // first string, then any number of numbersLabeled tuples improve readability: type Point = [x: number, y: number]. Optional tuple elements use ?: [string, number?].
Tuples are commonly used for function return types when destructuring: function useState<T>(initial: T): [T, (v: T) => void].
any: Opts out of type checking entirely. A value of type any can be assigned to and from any type — no errors, no safety. Use any as a last resort or during migration from JavaScript.
unknown: The type-safe counterpart to any. A value of type unknown can hold anything, but you must narrow it before using it: if (typeof x === 'string') { x.toUpperCase(); }. Use unknown for values from external sources (API responses, parsed JSON, user input).
never: Represents values that never occur — functions that always throw, exhausted type narrowing, and impossible intersections (string & number = never). Variables typed as never can't be assigned any value. never is the bottom type — it's assignable to every type but nothing is assignable to it.
void: Return type for functions that don't return a meaningful value. Different from undefined — a void function can technically return any value, but callers shouldn't use it. The primary use is callback typing: (callback: () => void) => void.
object: Represents any non-primitive type (not string, number, boolean, bigint, symbol, null, or undefined). Rarely used directly — prefer specific interface or type alias shapes.
Type assertions tell the compiler to treat a value as a specific type: value as string or <string>value (JSX conflicts with angle bracket syntax). Assertions don't perform runtime conversion — they're a compile-time override. Use sparingly; prefer type guards for safe narrowing.
TypeScript primitives mirror JavaScript: string, number, boolean, bigint, symbol, null, undefined. Literal types narrow to exact values (const infers literals, let infers widened types). any opts out of checking; unknown requires narrowing before use — always prefer unknown for external data. never is the bottom type for impossible values. Tuples are fixed-length arrays with per-position types.
Fun Fact
The unknown type was only added in TypeScript 3.0 (2018) — for the first six years of TypeScript, any was the only way to represent an arbitrary value. The addition of unknown is considered one of the most important type safety improvements in TypeScript's history, because it finally gave developers a way to say 'I don't know the type' without losing all type checking.