Original notes for JavaScript360.org — topic order inspired by Eloquent JavaScript (4th ed.) by Marijn Haverbeke. See the full attribution note.
Chapter 1: Values, Types & Operators
Estimated time: ~35 minutes · Chapter type: Concept
What you'll learn
- The handful of primitive value types JavaScript actually has
- How numbers work under the hood, and why
0.1 + 0.2isn't0.3 - Strings, template literals, and the operators you'll use every day
- JavaScript's much-mocked (and much-misunderstood) automatic type conversion
Values are the atoms of a program
Every program that does anything manipulates values — pieces of data like the number 42, the text "hello", or true. Every value in JavaScript has a type, and the type determines what you can do with it. There are only a handful of primitive types to learn: number, string, boolean, undefined, null, symbol, and bigint. Everything else you'll build (arrays, objects, functions) is a variation on the object type, which gets its own chapter.
You can always ask JavaScript what type a value is with the typeof operator:
typeof 42; // "number"
typeof "hi"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" — a decades-old bug that's now permanent, see below
Numbers
JavaScript has exactly one number type — there's no separate int vs float like in many other languages. Every number, whole or fractional, is stored as a 64-bit floating-point value, the IEEE 754 standard also used by most other modern languages.
let orders = 128;
let averageOrderValue = 47.5;
let distanceInMeters = 3.02e8; // scientific notation: 3.02 × 10^8
The floating-point representation is fast and flexible, but it can't represent every decimal fraction exactly — much like 1/3 can't be written exactly in decimal. That leads to the famous surprise:
0.1 + 0.2; // 0.30000000000000004
This isn't a JavaScript bug; it's how binary floating-point math works in essentially every mainstream language. The practical lesson: never compare floating-point numbers with === when you expect a "round" result — compare the difference against a small tolerance instead:
function roughlyEqual(a, b, epsilon = 1e-9) {
return Math.abs(a - b) < epsilon;
}
roughlyEqual(0.1 + 0.2, 0.3); // true
Arithmetic on numbers works as you'd expect: +, -, *, /, and % (remainder, not "modulo" in the strict mathematical sense once negative numbers are involved). There's also ** for exponentiation.
17 % 5; // 2 — remainder after dividing 17 by 5
2 ** 10; // 1024
Two special numeric values are worth knowing early:
Infinity/-Infinity— what you get from dividing by zero, or a value too large to represent.NaN("Not a Number") — the result of an undefined numeric operation, like0 / 0or parsing"abc"as a number.NaNhas the bizarre property that it's never equal to anything, including itself — useNumber.isNaN(value)to test for it, nevervalue === NaN.
NaN === NaN; // false — surprising, but consistent with IEEE 754
Number.isNaN(NaN); // true — the correct way to check
Strings
Strings hold text, wrapped in single quotes, double quotes, or backticks. Backticks give you template literals, which let you embed expressions directly and span multiple lines — reach for these by default in modern code.
const first = 'Grace';
const last = "Hopper";
const bio = `${first} ${last} helped popularize the term "debugging."`;
Strings are effectively arrays of characters (technically, UTF-16 code units — more on the distinction in Chapter 9). You can index into them, check their length, and call a long list of built-in methods:
const word = 'JavaScript';
word.length; // 10
word[0]; // "J"
word.toUpperCase(); // "JAVASCRIPT"
word.slice(0, 4); // "Java"
word.includes('Script'); // true
Strings, like numbers, are immutable — none of these methods change the original string; they all return a new one. word.toUpperCase() doesn't touch word; you have to capture the return value if you want to keep it.
You can glue strings together with +, but template literals are almost always the cleaner choice once more than one value is involved:
const price = 19.99;
'The item costs $' + price + ' today.'; // works, but clunky
`The item costs $${price} today.`; // clearer
Unary operators
A unary operator takes a single value. You've already used one: typeof. Two others come up constantly:
-valuenegates a number.!valueflips a boolean — and coerces its operand to a boolean first if it isn't one already (see "truthy and falsy" below).
!true; // false
!0; // true — 0 is "falsy"
!!'hello'; // true — double negation is a common idiom for "coerce to boolean"
Boolean values and comparisons
Boolean has exactly two values: true and false. You produce them with comparison operators (>, <, >=, <=, ===, !==) and combine them with logical operators:
&&(AND) —trueonly if both sides are true||(OR) —trueif at least one side is true!(NOT) — flips a boolean
Crucially, && and || short-circuit: they stop evaluating as soon as the result is determined. This isn't just an optimization — it's a pattern you'll use constantly to write conditional logic compactly:
function greet(user) {
return user && user.name; // only reads user.name if user isn't null/undefined
}
const config = userSetting || defaultSetting; // fall back to a default
There's a third logical operator worth knowing from day one: ??, the nullish coalescing operator. Unlike ||, which falls back on any falsy value (0, "", false included), ?? only falls back when the left side is specifically null or undefined — which is usually what you actually meant:
const quantity = 0;
quantity || 10; // 10 — probably not what you wanted, 0 got treated as "empty"
quantity ?? 10; // 0 — correctly preserves an intentional zero
Empty values: undefined and null
JavaScript has two different "nothing" values, which trips up a lot of newcomers:
undefinedmeans "this hasn't been given a value" — it's what a variable holds before assignment, what a missing function argument defaults to, and what you get accessing a property that doesn't exist.nullmeans "this is deliberately empty" — you assign it yourself to say "there's no value here, on purpose."
let assignee;
console.log(assignee); // undefined — nobody's been assigned yet
let winner = null; // explicitly: nobody has won (yet)
In practice, treat them as interchangeable "absence of value" signals in most everyday code, but understand the distinction when you read other people's code or API documentation — a function's docs might specifically say it returns null (not undefined) when a search finds nothing, for example.
Automatic type conversion
This is the part of JavaScript that generates the most jokes — and the most confusion. When you use an operator on values of mismatched types, JavaScript tries to convert one or both operands so the operation makes sense, rather than throwing an error. The classic example:
'5' + 1; // "51" — the number is converted to a string, then concatenated
'5' - 1; // 4 — the string is converted to a number, then subtracted
'five' - 1; // NaN — "five" can't become a number, so the result is NaN
The rule of thumb: + prefers strings (if either operand is a string, it concatenates), while every other arithmetic operator (-, *, /) prefers numbers and will try to convert strings to numbers.
This is exactly why you should almost always use === and !== (strict equality) instead of == and != (loose equality). Loose equality performs type conversion before comparing, and the rules are genuinely hard to memorize:
'' == 0; // true
0 == false; // true
null == undefined; // true
null == 0; // false — yes, really
None of these are things you want to reason about mid-debugging session. Strict equality (===) skips all conversion — if the types differ, the values are simply not equal, full stop:
'' === 0; // false
0 === false; // false
Rule for this course (and most production codebases): always use === / !==, and reach for == / != only in the one genuinely useful case — checking for "either null or undefined" with value == null.
Key takeaways
- JavaScript has one number type (64-bit floating point), so decimal math can be slightly imprecise — never test floats for exact equality.
- Strings are immutable; every string method returns a new string rather than modifying the original.
&&,||, and??all short-circuit and are used constantly for defaults and conditional logic — know the difference between||and??.undefinedmeans "no value was ever set";nullmeans "intentionally empty."- Prefer
===/!==over==/!=to avoid JavaScript's sometimes-surprising automatic type conversion.
Try it yourself
- Without running it, predict the output of
typeof (typeof 1). Then check your answer. - Write a one-line expression using
??that givessettings.timeouta default of30only when it'snullorundefined(not when it's legitimately0). - Explain, in your own words, why
'5' + 3 - 1evaluates to52rather than7. Walk through the two operators separately.
Hints
typeof 1is a string, andtypeofon a string always gives the same answer.- Nullish coalescing only falls through on
null/undefined, not on falsy-but-real values like0. - Operators run left to right when they have equal precedence: figure out what
'5' + 3becomes before the second operator runs.
Summary
Values have types, and JavaScript's type system is looser than most — operators will silently convert types rather than error out. That flexibility is convenient in small scripts and a common source of bugs in large ones, which is why disciplined JavaScript code leans on ===, explicit conversions (Number(x), String(x)), and tools like TypeScript once a codebase grows. Next, we zoom out from individual values to how statements and control flow assemble them into actual programs.