Values, Variables, and Operators
The primitive types JavaScript works with, how to declare and name bindings, and the operators and equality rules that combine values into new ones.
By the end of this lesson, you can
- Identify which of JavaScript's primitive types a given value belongs to, and explain why objects behave differently
- Choose between const and let, and explain what declaration and assignment each do
- Predict the result of comparing two values with === versus ==, and explain why === is preferred
- Recognize NaN and floating-point rounding as consequences of how numbers are represented, not bugs in your code
Why it matters
Almost every bug in a new JavaScript program traces back to one of two things: a value wasn’t the type you expected, or two values you thought were “the same” weren’t equal the way you meant. Both come down to understanding what a value actually is under the hood, and how JavaScript decides whether two of them match.
Mental model: primitives and objects
JavaScript has two fundamentally different kinds of value.
Primitives are simple, immutable values compared by their content: string, number,
boolean, undefined, null, plus bigint and symbol (both used rarely as a beginner — bigint
for integers larger than Number can represent exactly, symbol for unique, non-string property
keys).
let a = 5;
let b = 5;
console.log(a === b); // true — same content, so equal
Objects (including arrays and functions) are compared by identity, not content — two objects with identical contents are still different objects unless they’re literally the same one:
let obj1 = { count: 5 };
let obj2 = { count: 5 };
console.log(obj1 === obj2); // false — two different objects, even though the contents match
console.log(obj1 === obj1); // true — the exact same object
Declaring and naming
const declares a binding that can’t be reassigned; let declares one that can. Declaration
creates the binding; assignment gives it a value — let total = 0; does both at once, but they
are conceptually separate steps.
const taxRate = 0.08; // never reassigned — prefer const by default
let total = 0; // will be reassigned as items are added
total = total + 19.99; // assignment, no new declaration
Names should describe what a value represents, not its type (userCount, not numberVariable),
and can’t start with a digit or reuse a reserved word like class or return.
Operators, coercion, and equality
Arithmetic (+ - * / %), comparison (< > <= >=), and logical (&& || !) operators behave the way
you’d expect between two values of the same type. The trouble starts when the two sides don’t
match — and this is where === and == genuinely differ.
| Expression | Result | Why |
|---|---|---|
| 5 === 5 | true | Same type, same value. |
| 5 === "5" | false | === never coerces — a number and a string are never equal. |
| 5 == "5" | true | The string "5" is coerced to the number 5 first, then compared. |
| 0 == false | true | false is coerced to 0 for the comparison — surprising if unexpected. |
| null == undefined | true | A special-cased exception in the == rules. |
| NaN === NaN | false | NaN is defined to never equal anything, including itself. |
Template literals build strings without manual concatenation, and interpolate any expression:
const name = 'Ada';
const itemCount = 3;
console.log(`${name} has ${itemCount} item${itemCount === 1 ? '' : 's'} in the cart.`);Ada has 3 items in the cart.Truthiness and the newer operators
Every value is either truthy or falsy when used where a boolean is expected (an if
condition, for example). There are exactly six falsy values: false, 0, "", null,
undefined, and NaN. Everything else — including "0", [], and {} — is truthy.
Two operators lean on this directly, and both exist to handle “this might not be set” more safely than older patterns:
const config = { retries: 0 };
const retriesOld = config.retries || 3; // 3 — WRONG: 0 is falsy, so || replaces it
const retriesNew = config.retries ?? 3; // 0 — RIGHT: ?? only replaces null/undefined
const city = config.address?.city; // undefined, not a crash, if address doesn't exist
?? (nullish coalescing) only falls back when the left side is null or undefined — unlike
||, it leaves 0, "", and false alone. ?. (optional chaining) reads a property and short-
circuits to undefined instead of throwing if anything along the chain is missing.
Number edge cases
Numbers are stored as 64-bit floating-point values, which has two consequences worth knowing before they surprise you:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(10 / 0); // Infinity
console.log(0 / 0); // NaN
console.log(typeof NaN); // "number" — NaN is a number, just not a useful one0.1 + 0.2 isn’t a bug — most decimal fractions can’t be represented exactly in binary floating
point, so tiny rounding error is normal. Comparing floats for exact equality is therefore risky;
comparing within a small tolerance (or working in integer cents instead of decimal dollars) avoids
it. NaN means “not a valid number result,” and per the trace table above, NaN === NaN is
false — use Number.isNaN(x) to actually test for it.
Check your understanding
Or reveal the answer without checking
Answer:'no items' then 0
|| treats 0 as falsy and falls back to the string. ?? only falls back for null/undefined, and 0 is neither, so it's returned as-is.
Or reveal the answer without checking
Answer:"" == false
"" is coerced to false for ==, so "" == false is true. The others are false: === never coerces types, NaN never equals itself, and === never coerces null/undefined into matching each other (only == does).
Practice: warm-up
Predict the type (typeof) of each, then check: "42", 42, 42.0, true, null, undefined,
NaN, [1, 2, 3].
Stuck? Reveal one hint at a time.
Hint 1
Quotes always mean string, regardless of what characters are inside.
Hint 2
42 and 42.0 are both just the number 42 — JavaScript has one numeric type for both.
Hint 3
typeof null is a famous, long-standing quirk — it reports 'object', not 'null'. It's considered a mistake in the language that can't be fixed without breaking the web.
Hint 4
Arrays report typeof as "object" too — arrays are a specialized kind of object, covered in the next lesson.
Reveal the types
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
"42" -> string
42 -> number
42.0 -> number (same as 42 — no separate "float" type)
true -> boolean
null -> object (a well-known historical quirk, not "null")
undefined -> undefined
NaN -> number (a special, invalid numeric value)
[1, 2, 3] -> object (arrays are objects — more on this next lesson)Practice: apply it
Write a function normalizeInput(raw) that takes a value that might be a string, number, or
undefined (imagine it came from a text field) and returns a clean number: trim whitespace,
convert to a number, and return 0 if the result isn’t a valid number.
function normalizeInput(raw) {
// your code here
}
console.log(normalizeInput(' 42 ')); // 42
console.log(normalizeInput(17)); // 17
console.log(normalizeInput('abc')); // 0
console.log(normalizeInput(undefined)); // 0
Stuck? Reveal one hint at a time.
Hint 1
String.prototype.trim() removes leading/trailing whitespace, but only works on strings — you may need to convert first, or guard for non-strings.
Hint 2
Number(value) converts a value to a number, producing NaN for anything that doesn't parse cleanly.
Hint 3
Number.isNaN(result) is the safe way to check for that failure — remember result === NaN never works.
Reveal a reference solution
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
function normalizeInput(raw) {
const asNumber = Number(String(raw).trim());
return Number.isNaN(asNumber) ? 0 : asNumber;
}Summary
- Primitives (
string,number,boolean,undefined,null,bigint,symbol) compare by content; objects compare by identity. constis the default; useletonly when a binding needs to be reassigned.===never coerces types and is the default comparison;==follows a specific, memorizable-but- easy-to-forget list of coercion rules.- Exactly six values are falsy:
false,0,"",null,undefined,NaN. Everything else is truthy. ??falls back only fornull/undefined;?.short-circuits toundefinedinstead of throwing when a property chain is missing a link.0.1 + 0.2 !== 0.3andNaN === NaNisfalse— both are consequences of how numbers are represented, not mistakes in your code.