BeginnerValues, variables, and operators~35 min

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.

=== compares type and value; == coerces one side first
ExpressionResultWhy
5 === 5trueSame type, same value.
5 === "5"false=== never coerces — a number and a string are never equal.
5 == "5"trueThe string "5" is coerced to the number 5 first, then compared.
0 == falsetruefalse is coerced to 0 for the comparison — surprising if unexpected.
null == undefinedtrueA special-cased exception in the == rules.
NaN === NaNfalseNaN is defined to never equal anything, including itself.

Template literals build strings without manual concatenation, and interpolate any expression:

JavaScriptTemplate literals
const name = 'Ada';
const itemCount = 3;
console.log(`${name} has ${itemCount} item${itemCount === 1 ? '' : 's'} in the cart.`);
Output
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:

JavaScriptTwo well-known floating-point surprises
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 one

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

What does this log? const count = 0; console.log(count || 'no items'); console.log(count ?? 'no items');
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.

Which comparison below is true?
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.

  1. Hint 1

    Quotes always mean string, regardless of what characters are inside.

  2. Hint 2

    42 and 42.0 are both just the number 42 — JavaScript has one numeric type for both.

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

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

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

  2. Hint 2

    Number(value) converts a value to a number, producing NaN for anything that doesn't parse cleanly.

  3. 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.
  • const is the default; use let only 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 for null/undefined; ?. short-circuits to undefined instead of throwing when a property chain is missing a link.
  • 0.1 + 0.2 !== 0.3 and NaN === NaN is false — both are consequences of how numbers are represented, not mistakes in your code.