Topic 1.2
The Primitive Types
In one line
Primitives are the simplest values: text, numbers, true/false, big integers, symbols, null and undefined. Each has a lowercase type name, and a value like "left" can also be a type of its own.
Think of it like this
The basic ingredients in a kitchen: salt, flour, water, oil. You can't break them into simpler parts, and every recipe is built from them. Primitives are the ingredients of every program. Objects, arrays and functions are the dishes made from them.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Primitive
- A basic value that is not an object: string, number, boolean, bigint, symbol, null, undefined.
- string
- Text, written in quotes:
"hello",'hi'or a backtick template. - number
- Any ordinary number, whole or decimal, including
NaNandInfinity. - bigint
- A whole number of any size, written with an
nsuffix. It never mixes withnumber. - symbol
- A value guaranteed to be unique, made with
Symbol(). Used as a hidden key. - Literal type
- A type that holds exactly one value, like
"left",42ortrue. - Wrapper object
- An object like
new String("hi")that wraps a primitive. Avoid it; capitalStringis its type. - typeof
- A JavaScript operator that returns the runtime kind of a value as text.
- NaN
- A number value that means "not a number", e.g. the result of
0 / 0. It is still of typenumber.
Step by step
01The seven names
Each primitive has a lower-case type name, and the compiler keeps them apart: you can't put a number where a string is expected, or the other way round.
Text goes in quotes. bigint literals end in n. Symbol("tag") makes a new unique symbol every time you call it.
const name: string = "Asha";
let age: number = 30;
let price: number = 19.99;
let big: bigint = 9007199254740993n;
let ok: boolean = true;
const tag: symbol = Symbol("tag");
let nothing: null = null;
let missing: undefined = undefined;02One number type for everything
30 and 19.99 are both number. There is no int and no float.
The cost: decimals are stored in binary, so some are slightly off. 0.1 + 0.2 is 0.30000000000000004 in every language that uses 64-bit floats. Work with whole units (paise instead of rupees) when exact money matters.
03Why lower case matters
String (capital S) is the type of the wrapper object made by new String("hi"). It is not what you want for text.
A plain string can be assigned to String, but a String cannot be assigned back to string. The compiler tells you to prefer string. Just use lower case.
let good: string = "hello";
const wrapped: String = new String("hello");
let sneaky: string = wrapped;
// error TS2322: Type 'String' is not assignable to type 'string'.
// 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible.04Literal types: a type with one value
Put a value where a type should go and you get a literal type. let answer: 42 can only hold 42.
Join literals with | to list the allowed choices. "left" | "right" accepts exactly those two. Anything else, like "up", is an error with TS2322.
05Checking the kind at runtime with typeof
typeof gives the runtime kind as text. It matches the type names for most primitives, with one surprise: typeof null is "object".
The type system stays correct (the null type holds only null). You'll combine typeof with the checker in Topic 4.3 so that TypeScript narrows a value to one kind.
Try it yourself
- 1
Widen the choices
In the third example, change the first line to
"left" | "right" | "up". Predict which error disappears. The one ondirection = "up"is gone, because "up" is now on the list. - 2
Mix number and bigint
Add
console.log(big + 1);to the first example. Predict the error. TypeScript refuses withTS2365: Operator '+' cannot be applied to types 'bigint' and '1'. JavaScript would throw aTypeErrorat runtime; the checker saves you that. - 3
Spot the kind
Add
console.log(typeof NaN, typeof Infinity);to the first example and guess the output. Both arenumber.NaNliterally means "not a number" and is still of typenumber.
Code & diagrams
Notice const name is shown as the literal "Asha", not string: a const can never change, so TypeScript keeps the exact value (Topic 1.3). The console shows typeof null is object, and the famous 0.1 + 0.2 result.
Console
(nothing was printed)Expected console output
string number bigint boolean symbol object undefined
0.30000000000000004 9007199254740994nLook at the last console line: typeof the wrapper says object, not string. Change String to string in the first two annotations and the error goes away.
Console
(nothing was printed)Expected console output
string objectThree errors, three rules: only listed strings are allowed, a number literal is not a bigint, and a bigint literal is not a number. The program still runs and shows direction is now up, which the types said could never happen.
Console
(nothing was printed)Expected console output
up 42Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use the capital-S String
In the second example, the line let sneaky: string = wrapped; takes a String wrapper and tries to store it as a string.
Break #2
Put a number into a bigint
In the third example, let n: bigint = 10; gives a plain number to a bigint.
Myth vs fact
Myth
TypeScript has int, float and double like Java.
Fact
There is one number type for every ordinary number, plus bigint for exact huge integers. The difference between whole and decimal is not tracked by types.
Myth
String and string are two spellings of the same thing.
Fact
string is the primitive; String is the type of the rarely used wrapper object. Always write the lower-case one.
Myth
typeof null is "null", so TypeScript's null type must be broken.
Fact
The runtime answer "object" is a 1990s JavaScript bug that can't be fixed. The null type is correct and holds only null.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
A
constdeclaration keeps the literal type (const name: "Asha"), whileletwidens it tostring. This single rule, literal widening, explains many surprising errors; Topic 1.3 and Topic 4.2 cover it. - ▸
numberis an IEEE 754 double. Integers are exact up toNumber.MAX_SAFE_INTEGER(2^53 - 1). Beyond it, two different integers can be the samenumber. Types can't catch this:bigintis the only fix, and JSON has nobigint, so APIs usually send big ids as strings. - ▸
bigintliterals need the compiler target to be ES2020 or newer. In this course's setup (target: esnext) that is automatic.symbolhas a second, rarer typeunique symbolused for nominal tricks (Topic 10.4).
Remember this
- 1
The problem this solves: the checker can only tell you that you mixed up text and numbers if it has a name for each kind of value. These names are those basics. A primitive is a value that is not an object and has no parts you can change. JavaScript has exactly seven: string (text,
"hello"), number (42,19.99), boolean (trueorfalse), bigint (huge whole numbers, written with annlike9007199254740993n), symbol (a unique tag made withSymbol()), null and undefined (two different kinds of "nothing", Topic 1.7). TypeScript has one type name for each, all in lower case:string,number,boolean,bigint,symbol,null,undefined. - 2
numbercovers every ordinary number: whole, decimal, negative, plus the special valuesNaN("not a number") andInfinity. There is no separateintorfloat. JavaScript stores them all as 64-bit floating point, which is why0.1 + 0.2is0.30000000000000004(you'll see it run below). It is exact for whole numbers only up to 9,007,199,254,740,991. Past that, usebigint, which is exact for any size, but can't be mixed withnumber:10n + 1is an error in JavaScript andlet m: number = 10nis an error in TypeScript. - 3
Use the lower-case names. TypeScript also has capital-letter types
String,Number,Boolean,SymbolandBigInt. Those describe the wrapper objects (made bynew String("hi")), which almost nobody uses. A real string like"hi"is astring. It can be stored in aString("assignable" means "fits there"), but aStringcannot be stored back into astring. So a variable annotatedStringaccepts ordinary text and then refuses to flow into anything that wants astring. The compiler even says so: "'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible." - 4
A literal type is a type that holds exactly one value.
"left"is a type, and so are42andtrue. You write them as annotations:let answer: 42 = 42can never hold anything else. Their real power comes when you join several with|("or"):"left" | "right"accepts only those two strings. This is how TypeScript models a small fixed set of choices (Topic 4.2 goes deep). A variable declaredlet direction: "left" | "right"does not have the typestring: assigning"up"to it is an error. - 5
typeofis a JavaScript operator that tells you which kind a value is at runtime: it answers"string","number","bigint","boolean","symbol","undefined","function"or"object". One famous quirk:typeof nullis"object", an old JavaScript mistake that can never be fixed because too much code depends on it. TypeScript'snulltype is still correct: it holds onlynull. You will usetypeofto check kinds in Topic 4.3. - 6
Two related names often confused with primitives: **
object(any non-primitive: array, function, plain object) andvoid** (what a function returns when it returns nothing, Topic 2.3). Neither is a primitive. The arrays, objects and functions you build from primitives come in the next topics.
Explain it without notes
Name the seven primitive types in TypeScript and say what makes a value a primitive.
Why should you write string and not String?
What is a literal type and why would you use a union of literals?
Why can't you add a number to a bigint, and how do you fix it?
Practice
Declare speed that may only hold "slow", "normal" or "fast", starting at "normal". Then try to assign "turbo" and write down the error.
Add a type annotation to each variable, then say which one TypeScript would have inferred differently if you hadn't annotated it.
``ts
const count = 3;
let title = "Hectal";
const active = true;
Trade-offs
- ↔
A union of literals gives precise, typo-proof choices but must be kept in sync with the code that handles each choice. Topic 4.6 shows how the compiler can enforce that for you.
- ↔
bigintis exact but cannot be mixed withnumber, can't go throughJSON.stringifywithout help and is slower. Use it only when you really need integers beyond 2^53.
Done when you can
I can name the seven primitive types and write each type in lower case.
I can explain why
Stringis the wrong type for text.I can write a literal type and a union of literals.
I can say why
0.1 + 0.2is not exactly0.3and when to usebigint.I can explain the
typeof nullquirk.