Learn JavaScript

Lesson 3 of 8 · First Programs: The Console, let and const, Types, typeof

Module 1 · First Programs: The Console, let and const, Types, typeof

The Seven Primitives and One Object

FreeReading

In this lesson

  • Name JavaScript's seven primitive types and the object type, with one value of each.
  • Explain why 5 and 5.0 are the same number, and why 0.1 + 0.2 prints 0.30000000000000004.
  • Tell undefined from null, and say where whole numbers stop being exact.

Maria has done a little C. She asks what type 5 is, and what type 5.0 is. In C those are two answers, int and double, and they behave differently. In JavaScript the answer is the same word twice: number. This lesson shows the few kinds of value JavaScript has, and the edges of the one you will use most.

Every value has a type

A value's type is the kind of thing it is: a number, a piece of text, a yes or no. The type decides what an operator does with the value. 2 * 3 multiplies, while "two" * 3 has nothing sensible to do.

JavaScript has eight types. Seven of them are primitive: simple values that cannot be taken apart. The eighth, object, holds everything else.

TypeOne value of itWhat it is forHow often you will meet it
number42, 4.5, -3every number, whole or notevery day
string"Zara"text of any lengthevery day
booleantrue, falseyes or noevery day
undefinedundefinedno value yetoften, usually by surprise
nullnullno value, on purposeoften
bigint10nwhole numbers too big for numberrarely
symbolSymbol("id")a unique label, used inside librariesrarely
object{ name: "Zara" }everything else: lists, records, functionsfrom Module 7 on

You can ask a value for its type with typeof. It answers with the type's name, as a string. Lesson 04 is all about it; here it is one line.

console.log(typeof 42);
console.log(typeof "Zara");
console.log(typeof true);
console.log(typeof 10n);
number
string
boolean
bigint

The type belongs to the value, not to the name. A let that holds a number can hold a string after the next assignment, as the boxes below show. So every value carries one of eight types, and three of them, number, string and boolean, do most of the work.

Six lines fill five boxes. count holds 3, a number, and price holds 4.5, also a number. name holds "Zara", a string, and done holds false, a boolean. nickname gets no value and holds undefined. On the last line count takes "three", and its type changes from number to string.

number: one type for 5 and 5.0

JavaScript has one number type for whole numbers and decimals. 5 and 5.0 are the same value, and console.log prints both as 5.

console.log(typeof 5);
console.log(typeof 5.0);
console.log(5.0);
console.log(5.50);
console.log(7 / 2);
number
number
5
5.5
3.5

Inside the engine, every number is stored in 64 bits in a format called IEEE 754 double precision. That one choice explains three things you will meet. Division keeps the fraction, decimals are not always exact, and very large whole numbers lose their last digits.

If you know C

A JavaScript number is C's double, for every number, whole ones included. There is no int, long or float. So everything you learned about double in C applies here, including its rounding.

So 5 and 5.0 are one number, and the next three sections are the consequences of storing every number as a double.

Why 0.1 + 0.2 is not 0.3

A double stores a number in binary, as a sum of halves, quarters, eighths and so on. Most decimal fractions, such as 0.1, have no exact binary form, just as 1/3 has no exact decimal form. The engine stores the nearest double instead. Usually the tiny difference stays hidden, and sometimes it shows.

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
console.log(0.5 + 0.25 === 0.75);
console.log(Math.abs(0.1 + 0.2 - 0.3) < 1e-9);
0.30000000000000004
false
true
true

=== asks whether two values are exactly the same, and gives true or false; Module 2 covers it fully. 0.5 and 0.25 are a half and a quarter, exact in binary, so their sum is exact. 0.1 and 0.2 are not, and their sum misses 0.3 by about 0.00000000000000006.

The last line is the fix when you compare decimals. Math.abs gives the distance from zero, so it measures how far apart the two values are. 1e-9 is 0.000000001, written in the short form for powers of ten. If the gap is smaller than that, treat the values as equal. So never test two decimals with ===; test whether they are close.

NaN and Infinity

The number type has two values that are not ordinary numbers. Both are of type number.

console.log(0 / 0);
console.log("abc" * 2);
console.log(1 / 0);
console.log(-1 / 0);
console.log(typeof NaN);
console.log(NaN + 1);
NaN
NaN
Infinity
-Infinity
number
NaN

NaN means "not a number". It is what you get from a sum with no sensible answer, such as text times two. Yes, the type of "not a number" is number; it is a number value that marks a failed calculation. Anything done with NaN gives NaN, so one bad value spreads through every sum after it.

Infinity is the result of dividing a positive number by zero, and -Infinity of a negative one. JavaScript never stops the program for a division by zero, while C stops one for whole-number division. So NaN and Infinity are how the number type reports a sum that went wrong, and the program keeps running.

Where whole numbers stop being exact

A double has room for about 15 to 17 significant digits. Every whole number up to 253 - 1, which is 9007199254740991, is stored exactly. JavaScript names that edge Number.MAX_SAFE_INTEGER.

console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MAX_SAFE_INTEGER + 1);
console.log(Number.isSafeInteger(9007199254740991));
console.log(Number.isSafeInteger(9007199254740992));
console.log(1234567890123456789);
9007199254740991
9007199254740992
true
false
1234567890123456800

Read the last line again. The program said 1234567890123456789, and it printed a number ending in 800. Past the safe edge, the engine keeps only the nearest double it can hold, so the last digits change without any message. The number line shows where whole numbers are safe, and why gaps open past the edge.

The safe-integer edge: every whole number is exact up to 2 to the power 53 minus 1 Whole numbers in a JavaScript number 0 9007199254740991 Number.MAX_SAFE_INTEGER, 2^53 - 1 every whole number exact gaps open: only some whole numbers exist bigint: exact at any size, written 10n Past the edge, a number snaps to the nearest one the double can hold.

When you need exact whole numbers past the edge, JavaScript has bigint. Write an n after the digits. A bigint is a different type, and it is exact at any size.

console.log(2 ** 64);
console.log(2n ** 64n);
console.log(9007199254740993n + 1n);
18446744073709552000
18446744073709551616n
9007199254740994n

The first line is a double, rounded. The second is exact, and console.log shows the n so you can tell them apart. So whole numbers are exact up to Number.MAX_SAFE_INTEGER, and past it you either accept rounding or switch to bigint.

string and boolean

A string is text of any length, from the empty string "" to a whole book. Every string knows how many characters it has, through .length. Module 3 is all about strings.

console.log("Zara".length);
console.log("".length);
console.log(typeof "42");
console.log(5 > 3);
console.log(2 > 7);
console.log(typeof (5 > 3));
4
0
string
true
false
boolean

"42" shows digits but is a string, because of the quotes. A boolean is one of two values, true or false. You rarely type them; comparisons such as 5 > 3 produce them, and Module 4 makes decisions with them. So a string is text in quotes, and a boolean is the answer to a yes-or-no question.

undefined and null: two kinds of nothing

JavaScript has two values that mean "nothing here", and they do not mean the same thing.

undefinednull
Meansno value yetno value, on purpose
Who puts it therethe engine, by defaultyou, the author
Examplelet nickname;const middleName = null;
Read it as"nobody set this""someone checked, and there is none"
let nickname;
const middleName = null;
console.log(nickname);
console.log(middleName);
undefined
null

Amara has no middle name, so her form stores null: the question was asked, and the answer is "none". A name that was declared and never given a value holds undefined. So write null yourself when "none" is the answer, and treat a surprise undefined as a sign that something was never set. Lesson 04 tracks where it comes from.

bigint, symbol and object, briefly

bigint arrived in ES2020 for exact whole numbers past the safe edge: database ids, cryptography, contest sums. You will use it once in this track's Module 2 problems, and rarely after.

symbol arrived in ES2015. A symbol is a label that is always unique, even from another symbol with the same description. Libraries and the language itself use them. You can write code for years without making one, and this track meets it in Module 16, beside iterators.

object is the type of everything that is not a primitive: a list like [1, 2, 3], a record like { name: "Zara", age: 14 }, and even a function. Modules 7 and 8 open it up. So of the eight types, you now know the five you will use from this week on.

Example 1: one value of each everyday type

The smallest tour: five values, five typeof answers.

console.log(typeof 2026);
console.log(typeof 19.99);
console.log(typeof "2026");
console.log(typeof false);
console.log(typeof undefined);
number
number
string
boolean
undefined

2026 and 19.99 share a type. "2026" does not, because of its quotes.

Run in Compiler
Example 2: money in whole paisa

Zara prices three pencils at 2.99 taka each, then one pencil and one eraser at 1.99. She works each out two ways: in taka with decimals, and in whole paisa, the hundredths of a taka.

console.log(2.99 * 3);
console.log(299 * 3);
console.log(299 * 3 / 100);
console.log(1.99 + 2.99);
console.log((199 + 299) / 100);
8.97
897
8.97
4.98
4.98

Here the decimals happened to come out right. They often do, and that is the trap: 0.1 + 0.2 showed that they do not always. Whole paisa are exact up to the safe edge, so a shop keeps prices as whole numbers and divides by 100 only to print.

Run in Compiler
Example 3: the closeness test, written once

Kenji wants one line that says whether two decimals are equal for practical purposes.

const a = 0.1 + 0.2;
const b = 0.3;
console.log("exact:", a === b);
console.log("close:", Math.abs(a - b) < 1e-9);
console.log("gap:", Math.abs(a - b));
exact: false
close: true
gap: 5.551115123125783e-17

The gap is 5.55 times 10-17, far below 1e-9. The graded problem close-enough uses exactly this test.

Run in Compiler
Example 4: a type that moves with its value

Bob reuses one let for two jobs. The type follows whatever it holds now.

let reading = 36.6;
console.log(reading, typeof reading);
reading = "not taken";
console.log(reading, typeof reading);
reading = null;
console.log(reading);
36.6 number
not taken string
null

JavaScript allows this, and it makes programs hard to follow. One name for one kind of value is the habit worth keeping. TypeScript, a language built on JavaScript, exists largely to enforce that habit.

Run in Compiler

Where this is used

  • Stripe. The payments API takes every amount as a whole number in the smallest unit of the currency: 1099 means 10.99 dollars. Whole cents are exact in a double, so no rounding creeps into a charge.
  • X, formerly Twitter. Post ids are 64-bit whole numbers, past the safe edge. The older v1.1 API sent each id twice, as a number and as a string in id_str, because a JavaScript number would change the last digits. The v2 API sends ids as strings only.
  • decimal.js. Programs that must do exact decimal arithmetic, such as invoices with tax, use a decimal library like this one instead of plain numbers.

Common mistakes

1. Testing decimals with ===.

const total = 0.1 + 0.2;
console.log(total === 0.3);

No message at all: it prints false. The values differ in the 17th digit. Test closeness instead: Math.abs(total - 0.3) < 1e-9. You will reach for === because on paper the sum is exactly 0.3.

2. Mixing a bigint and a number.

console.log(10n + 1);

TypeError: Cannot mix BigInt and other types, use explicit conversions. The engine will not guess which type you wanted. Write both as bigints, 10n + 1n. You will forget the second n because the 1 looks too small to matter.

3. A long id stored as a number.

const orderId = 1234567890123456789;
console.log(orderId);

No message: it prints 1234567890123456800, which is a different order. Keep ids as strings, "1234567890123456789", or as bigints. You will store it as a number because it is made of digits.

4. Quotes that turn a number into text.

const age = "14";
console.log(typeof age);

It prints string. With quotes, 14 is text, and Module 2 shows what + does with that. Write const age = 14;. You will add the quotes when the value came from a form or a file, where everything arrives as text.

Brain teaser

The lesson printed Number.MAX_SAFE_INTEGER + 1. Kenji asks for one more step.

console.log(Number.MAX_SAFE_INTEGER + 2);

On paper the answer is 9007199254740993. What does the program print, and why that number?

Look at the number line again. Past the edge, which whole numbers can a double still hold?

Exercise 1Easy

Alice is checking a form before it is saved. Its five values are 2026, "2026", false, 9.99 and 10n. Print the type of each, one per line, in that order.

Output. Five lines.

number
string
boolean
number
bigint
const year = 2026;
const yearText = "2026";
const agreed = false;
const price = 9.99;
const bigCount = 10n;

// Print typeof each one, in the order above.

Not graded. The graded version, type-of-each, reads its values from the input and comes after lessons 04 and 05.

Run in Compiler
Exercise 2Easy

Zara does not trust 0.1 + 0.2. Print whether it is exactly 0.3, then whether it is within 1e-9 of 0.3.

Output. Two lines.

false
true
const sum = 0.1 + 0.2;

// Line 1: is sum exactly 0.3? Use ===.
// Line 2: is the distance between sum and 0.3 less than 1e-9?

Not graded. The graded version, close-enough, reads three numbers from the input.

Run in Compiler

Common doubts

  • Why does JavaScript have only one number type?

    It was designed in 1995 for small scripts on web pages, where one simple number type was enough. The cost is the rounding in this lesson. ES2020 added bigint for the cases where exact whole numbers matter.

  • Is there a type for a single character, like C's char?

    No. "a" is a string whose length is 1. A string of one character and a string of a thousand are the same type.

  • When should I write null, and when undefined?

    Write null yourself when "none" is the real answer. Leave undefined to the engine, so that seeing it always means "this was never set". Many teams follow that split, and lesson 04 shows why it helps.

  • Does the rounding mean JavaScript is bad at maths?

    No more than any language that uses doubles. Python and Java print the same 0.30000000000000004 for this sum, and C stores the same double. The difference is that JavaScript uses a double for every number, so you meet it sooner.

Key takeaways

  • JavaScript has seven primitive types (number, string, boolean, undefined, null, bigint, symbol) and object.
  • number is one type for whole numbers and decimals, stored as a 64-bit double.
  • Decimals are not always exact, so 0.1 + 0.2 is 0.30000000000000004; compare decimals by closeness.
  • NaN and Infinity report a failed or infinite sum, and the program keeps running.
  • Whole numbers are exact up to Number.MAX_SAFE_INTEGER; past it, use bigint or strings.
  • undefined means no value yet; null means no value, on purpose.

Next, lesson 04 asks values what they are with typeof, and meets the answer that has been wrong since 1995.

End of lesson 3

Mark it done, and your progress moves with you.

Next: typeof, undefined and null: Asking a Value What It Is