Primitive Types
Numbers, strings, booleans, and other atomic types that form the foundation of TypeScript's type system
TypeScript's type system begins with primitives — the atomic,
indivisible types that represent single values. If you already know
JavaScript, you've used these types hundreds of times: numbers, strings,
booleans, null, undefined. TypeScript gives you a way to name these
types in your code, so the compiler can verify that a variable holding a
number never accidentally receives a string.
This chapter answers three questions: What are the primitive types? How does the compiler track them? What bugs does this prevent?
The seven primitive types
JavaScript has seven primitive types. TypeScript provides a corresponding type annotation for each:
number— all numeric values, integers and floats alikestring— text valuesboolean—trueorfalsenull— the intentional absence of a valueundefined— uninitialized or missing valuesbigint— arbitrarily large integers (ES2020+)symbol— unique, immutable identifiers
Here's a simple demonstration of each:
What the compiler now knows: Every one of these variables has a fixed
type. If you later try to assign greeting = 99, TypeScript raises a
compile-time error — it knows greeting is a string, and 99 is a
number.
Type inference vs explicit annotation
You don't always need to write the type. TypeScript infers it from the value:
The compiler sees x = 10 and concludes x: number. No annotation
required. But when you want to document intent or when the right-hand
side is ambiguous, you can (and should) annotate explicitly:
What bugs does this prevent? If you accidentally assign userId = "abc", the compiler halts compilation. The type annotation acts as a
machine-checked contract.
The difference between number and Number
JavaScript has both a primitive number type and a wrapper object
Number. TypeScript mirrors this distinction, but you should almost
always use lowercase number. The capitalized Number refers to the
wrapper object, which is almost never what you want:
// Good: primitive type
let count: number = 42;
// Bad: object wrapper (deprecated in TypeScript)
let boxed: Number = new Number(42);The same rule applies to string vs String and boolean vs Boolean.
Always use the lowercase primitive types. The capitalized versions exist
only for edge cases (like when you need to call String.fromCharCode
directly) and should not appear in type annotations.
Why does TypeScript flag the capitalized versions? Because the wrapper
objects behave differently. typeof (new Number(42)) is "object", not
"number". Mixing them leads to subtle bugs. The compiler steers you
toward the primitive types by making the wrappers a lint warning in most
configurations.
The null and undefined divide
JavaScript has two ways to represent "no value": null and undefined.
TypeScript treats them as distinct types:
In practice, undefined means "never initialized" or "missing property,"
while null means "intentionally empty." By giving each its own type,
TypeScript forces you to be explicit about which kind of emptiness you
expect.
Strict null checks: By default (in strict mode), TypeScript does
not allow null or undefined to inhabit other types. A variable of
type number cannot hold null unless you explicitly declare it as
number | null. This is a major source of TypeScript's safety — the
billion-dollar mistake (null pointer errors) becomes a compile-time issue,
not a runtime crash.
Literal types: a preview
Every primitive value is also a literal type. The number 42 is not
just a number — it's specifically the number 42. TypeScript can
track this:
When you declare a variable with const, TypeScript infers the literal
type. When you use let, it infers the broader primitive type (because
let allows reassignment). We'll explore literal types in depth in
Type Aliases and Literals, but the key
insight is this: TypeScript's type system is more precise than "just a
number" — it can track the exact value when the code makes that
guarantee.
BigInt and Symbol
Modern JavaScript (ES2020+) includes two more primitives: bigint for
arbitrarily large integers, and symbol for unique identifiers.
bigint handles integers beyond JavaScript's safe integer range
(Number.MAX_SAFE_INTEGER, which is 253 − 1). You write them
with an n suffix:
symbol creates a unique, immutable value. No two symbols are equal,
even if they have the same description:
Symbols are rarely used in beginner code, but they're essential for
metaprogramming (like defining well-known symbols such as
Symbol.iterator). TypeScript tracks them as the type symbol.
Primitive type hierarchy (a mental model)
Imagine the type system as a lattice. At the very top sits unknown (the
type that accepts any value). At the very bottom sits never (the type
with no values). Primitive types occupy the middle:
Every primitive is a subtype of unknown and a supertype of never.
We'll explore unknown and never in detail later (see
Any, Unknown, Never), but for now, remember: the
primitive types are the middle layer, neither maximally permissive
(like unknown) nor maximally restrictive (like never).
Why primitive types matter
Consider a JavaScript function that calculates the area of a rectangle:
function area(width, height) {
return width * height;
}Nothing stops you from calling area("10", 5) — JavaScript will coerce
the string "10" to a number, and you'll get 50. But what if the string
were "10px"? You'd get NaN. The bug hides until runtime.
Now add types:
The line area("10", 5) is a compile-time error. The compiler knows
that "10" is a string, and area requires number. The bug is caught
before you ever run the code.
This is the core value proposition of primitive types: They turn
category errors (passing the wrong kind of data) into compile-time errors,
not runtime NaN or TypeError exceptions.
Practice: temperature converter
Let's solidify these concepts with a small challenge. You'll write a function that converts Celsius to Fahrenheit. The twist: the compiler should enforce that both input and output are numbers.
Implement the function celsiusToFahrenheit(c: number): number that converts a Celsius temperature to Fahrenheit. The formula is F = C × 9/5 + 32. The test will verify that 0°C = 32°F and 100°C = 212°F.
Check your understanding
Which of the following is the correct way to annotate a variable that holds a number in TypeScript?
let x: Number = 10;
let x: number = 10;
let x: num = 10;
let x = Number(10);
What is the type of the variable x after this declaration: const x = 42;?
number (the general number type)
42 (the literal type)
any
unknown
Summary
Primitive types are the atoms of TypeScript's type system. By annotating
variables with number, string, boolean, null, undefined,
bigint, or symbol, you tell the compiler what kind of data a variable
can hold. The compiler then enforces these annotations at every assignment
and function call, catching category errors before they become runtime
bugs.
In the next chapter, Arrays and Tuples, we'll see how to build compound types from these primitives — collections of values whose shape and length the compiler can verify.