Primitive Types
Primitives are the atomic building blocks of every TypeScript program — the simple, immutable values that are not objects. TypeScript mirrors JavaScript’s seven primitive types exactly, adding compile-time checking on top. Knowing them precisely matters because subtle traps await the unwary: there is no separate integer type, bigint needs a modern target, and the capitalized wrapper types are almost never what you want. This page walks through each primitive, its literal-type subtypes, and the wrapper-object pitfall.
The seven primitives
TypeScript has exactly seven primitive types, each written in lowercase. Everything else — objects, arrays, functions — is non-primitive.
let title: string = "TypeScript";
let count: number = 42;
let isReady: boolean = true;
let big: bigint = 9007199254740993n;
let id: symbol = Symbol("id");
let nothing: null = null;
let missing: undefined = undefined;
These cover the full set of values JavaScript treats as primitives. The remainder of this page details the behavior of each, since the lowercase spelling and a few runtime quirks trip up newcomers regularly.
| Primitive | Example value | Notes |
|---|---|---|
string | "hello" | Text; single, double, or template literals |
number | 3.14, 42, 0xff | All numerics — ints and floats |
boolean | true, false | Logical values only |
bigint | 100n | Arbitrary-precision integers; needs ES2020+ |
symbol | Symbol("k") | Unique, immutable identifiers |
null | null | Intentional absence of a value |
undefined | undefined | Uninitialized / missing value |
number covers ints and floats
Unlike many languages, TypeScript has no int, float, or double — a single number type represents every numeric value as an IEEE 754 double. Hex, octal, binary, and underscore separators are all number.
const integer: number = 42;
const float: number = 3.14159;
const hex: number = 0xff; // 255
const binary: number = 0b1010; // 10
const big: number = 1_000_000; // underscores aid readability
const notANumber: number = NaN; // also a number
Because there is no integer subtype, 7 / 2 is 3.5, not 3. When you need exact large integers beyond Number.MAX_SAFE_INTEGER (2^53 − 1), reach for bigint instead.
bigint for large integers
bigint represents integers of arbitrary size. A bigint literal is written with an n suffix, and the type requires a compilation target of ES2020 or later.
const huge: bigint = 9_007_199_254_740_993n;
const fromCtor = BigInt(42); // also a bigint
const sum = huge + 1n; // ✅ bigint arithmetic
// ❌ Error: Operator '+' cannot be applied to types 'bigint' and 'number'.
const mixed = huge + 1;
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020" // or later, required for bigint literals
}
}
You cannot mix bigint and number in arithmetic — TypeScript rejects it because the implicit conversion would lose precision. Convert explicitly with BigInt(...) or Number(...) when you must cross the boundary.
string, boolean, and symbol
string holds text in single quotes, double quotes, or backtick template literals. boolean is strictly true or false. symbol produces unique, immutable values often used as non-colliding object keys.
const name: string = `Ada Lovelace`;
const enabled: boolean = name.length > 0;
const key: symbol = Symbol("session");
const obj = { [key]: "secret" }; // unique, collision-free property key
// Every Symbol() call is unique
console.log(Symbol("x") === Symbol("x")); // false
Template literals are still just string at the value level (their precise form powers literal types). Two symbols are never equal even with the same description, which is exactly what makes them safe as private-ish keys.
null and undefined
null and undefined each have their own type and represent the two flavors of “no value”. Under strictNullChecks they are not assignable to other types unless you opt in with a union.
let maybeName: string | null = null;
maybeName = "Grace"; // ✅
let opt: string | undefined; // undefined until assigned
// ❌ Error (with strictNullChecks): Type 'null' is not assignable to type 'string'.
let bad: string = null;
This is one of TypeScript’s most valuable safety features. The full treatment — optional chaining, nullish coalescing, and non-null assertions — lives in null and undefined.
The wrapper-object pitfall
JavaScript exposes capitalized constructors — String, Number, Boolean — for the wrapper objects around primitives. As TypeScript types they refer to those object types, not the primitives, and you should never annotate with them.
// ❌ Wrong: the wrapper object type
let name: String = "Ada";
// ✅ Right: the primitive type
let proper: string = "Ada";
// Why it bites: a primitive is assignable to its wrapper, but not vice versa
const wrapped: String = "x";
const prim: string = wrapped; // ❌ Error: 'String' is not assignable to 'string'.
A string (lowercase) is assignable to a String (the wrapper), but not the other way around, so wrapper annotations quietly break compatibility with the rest of your typed code. Rule of thumb: always use the lowercase primitive types.
The capitalized
String,Number, andBooleantypes are a near-universal mistake. Most linters flag them via theban-typesrule. If you see an uppercase primitive in an annotation, it is almost certainly wrong.
Literal types as subtypes
Every primitive has literal subtypes — a single exact value. A literal type like "GET" is a subtype of string, and unions of literals model finite sets of allowed values precisely.
let method: "GET" | "POST" = "GET";
let answer: 42 = 42;
let flag: true = true;
method = "POST"; // ✅
// ❌ Error: Type '"DELETE"' is not assignable to type '"GET" | "POST"'.
method = "DELETE";
Literal types are how TypeScript expresses “this string must be one of these exact values,” turning a loose string into a tight, self-documenting contract. They are covered in depth in Literal Types and Union Types.
Best Practices
- Always use lowercase
string,number,boolean— never the capitalized wrapper types. - Remember
numbercovers integers and floats alike; there is no separate integer type. - Use
bigint(withnliterals and an ES2020+ target) only when you need precision beyond 2^53 − 1. - Never mix
bigintandnumberin arithmetic; convert explicitly at the boundary. - Keep
strictNullCheckson and model absence with explicit| nullor| undefinedunions. - Prefer literal types and unions over bare primitives when a value has a fixed set of options.
- Use
symbolfor collision-free keys rather than magic string properties.