Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~20 min· topic 2 of 7

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 NaN and Infinity.
bigint
A whole number of any size, written with an n suffix. It never mixes with number.
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", 42 or true.
Wrapper object
An object like new String("hi") that wraps a primitive. Avoid it; capital String is 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 type number.

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.

primitives.tswhole filets
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.

wrappers.tswhole filets
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.

Literal types: a type with one valuediagram
Rendering diagram…

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. 1

    Widen the choices

    In the third example, change the first line to "left" | "right" | "up". Predict which error disappears. The one on direction = "up" is gone, because "up" is now on the list.

  2. 2

    Mix number and bigint

    Add console.log(big + 1); to the first example. Predict the error. TypeScript refuses with TS2365: Operator '+' cannot be applied to types 'bigint' and '1'. JavaScript would throw a TypeError at runtime; the checker saves you that.

  3. 3

    Spot the kind

    Add console.log(typeof NaN, typeof Infinity); to the first example and guess the output. Both are number. NaN literally means "not a number" and is still of type number.

Code & diagrams

Every primitive and its runtime kind

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 9007199254740994n
string versus the wrapper String

Look 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 object
Literal types and the number/bigint wall

Three 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 42

Break 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.

terminal
$ Types panel
── what you'll see ──
index.ts(5,5): 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.

Break #2

Put a number into a bigint

In the third example, let n: bigint = 10; gives a plain number to a bigint.

terminal
$ Types panel
── what you'll see ──
index.ts(7,5): error TS2322: Type 'number' is not assignable to type '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 const declaration keeps the literal type (const name: "Asha"), while let widens it to string. This single rule, literal widening, explains many surprising errors; Topic 1.3 and Topic 4.2 cover it.

  • ▸

    number is an IEEE 754 double. Integers are exact up to Number.MAX_SAFE_INTEGER (2^53 - 1). Beyond it, two different integers can be the same number. Types can't catch this: bigint is the only fix, and JSON has no bigint, so APIs usually send big ids as strings.

  • ▸

    bigint literals need the compiler target to be ES2020 or newer. In this course's setup (target: esnext) that is automatic. symbol has a second, rarer type unique symbol used for nominal tricks (Topic 10.4).

Remember this

  1. 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 (true or false), bigint (huge whole numbers, written with an n like 9007199254740993n), symbol (a unique tag made with Symbol()), 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. 2

    number covers every ordinary number: whole, decimal, negative, plus the special values NaN ("not a number") and Infinity. There is no separate int or float. JavaScript stores them all as 64-bit floating point, which is why 0.1 + 0.2 is 0.30000000000000004 (you'll see it run below). It is exact for whole numbers only up to 9,007,199,254,740,991. Past that, use bigint, which is exact for any size, but can't be mixed with number: 10n + 1 is an error in JavaScript and let m: number = 10n is an error in TypeScript.

  3. 3

    Use the lower-case names. TypeScript also has capital-letter types String, Number, Boolean, Symbol and BigInt. Those describe the wrapper objects (made by new String("hi")), which almost nobody uses. A real string like "hi" is a string. It can be stored in a String ("assignable" means "fits there"), but a String cannot be stored back into a string. So a variable annotated String accepts ordinary text and then refuses to flow into anything that wants a string. The compiler even says so: "'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible."

  4. 4

    A literal type is a type that holds exactly one value. "left" is a type, and so are 42 and true. You write them as annotations: let answer: 42 = 42 can 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 declared let direction: "left" | "right" does not have the type string: assigning "up" to it is an error.

  5. 5

    typeof is 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 null is "object", an old JavaScript mistake that can never be fixed because too much code depends on it. TypeScript's null type is still correct: it holds only null. You will use typeof to check kinds in Topic 4.3.

  6. 6

    Two related names often confused with primitives: **object (any non-primitive: array, function, plain object) and void** (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

01

Name the seven primitive types in TypeScript and say what makes a value a primitive.

02

Why should you write string and not String?

03

What is a literal type and why would you use a union of literals?

04

Why can't you add a number to a bigint, and how do you fix it?

Practice

01

Declare speed that may only hold "slow", "normal" or "fast", starting at "normal". Then try to assign "turbo" and write down the error.

02

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.

  • ↔

    bigint is exact but cannot be mixed with number, can't go through JSON.stringify without 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 String is the wrong type for text.

  • I can write a literal type and a union of literals.

  • I can say why 0.1 + 0.2 is not exactly 0.3 and when to use bigint.

  • I can explain the typeof null quirk.