Skip to content
TypeScript ts basic-types 5 min read

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.

PrimitiveExample valueNotes
string"hello"Text; single, double, or template literals
number3.14, 42, 0xffAll numerics — ints and floats
booleantrue, falseLogical values only
bigint100nArbitrary-precision integers; needs ES2020+
symbolSymbol("k")Unique, immutable identifiers
nullnullIntentional absence of a value
undefinedundefinedUninitialized / 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, and Boolean types are a near-universal mistake. Most linters flag them via the ban-types rule. 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 number covers integers and floats alike; there is no separate integer type.
  • Use bigint (with n literals and an ES2020+ target) only when you need precision beyond 2^53 − 1.
  • Never mix bigint and number in arithmetic; convert explicitly at the boundary.
  • Keep strictNullChecks on and model absence with explicit | null or | undefined unions.
  • Prefer literal types and unions over bare primitives when a value has a fixed set of options.
  • Use symbol for collision-free keys rather than magic string properties.
Last updated June 29, 2026
Was this helpful?