Variables and Data Types
Table of Contents
In the Introduction to JavaScript, we touched on let, const, and the basic data types. This tutorial goes deeper — we’ll explore how JavaScript handles types under the hood, what type coercion actually does, and the practical patterns you’ll use daily like template literals, destructuring, and type checking.
var, let, and const in Depth
You already know the basics: use const by default, let when you need to reassign. But the differences go beyond reassignment — they affect scoping and hoisting.
Block Scope vs Function Scope
let and const are block-scoped — they only exist inside the {} where they’re declared. var is function-scoped, which means it ignores block boundaries:
function example() {
if (true) {
var x = 1;
let y = 2;
const z = 3;
}
console.log(x); // 1 — var leaks out of the if block
// console.log(y); // ReferenceError — let is block-scoped
// console.log(z); // ReferenceError — const is block-scoped
}
example();
This is the main reason var causes bugs. In a for loop, it’s especially problematic:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3 — not 0, 1, 2!
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// Prints: 0, 1, 2 — each iteration gets its own j
Hoisting
JavaScript moves variable declarations to the top of their scope before execution. But the behavior differs:
console.log(a); // undefined — var is hoisted and initialized to undefined
var a = 5;
// console.log(b); // ReferenceError — let is hoisted but NOT initialized
let b = 10;
let and const are hoisted too, but they sit in a “temporal dead zone” from the start of the block until the declaration is reached. Accessing them before that throws an error, which is actually helpful — it catches mistakes early.
Primitive Types
JavaScript has seven primitive types. Each one is immutable — you can’t change the value itself, only reassign the variable to a new value.
// string
const name = "Alice";
// number (integers and floats are the same type)
const age = 25;
const price = 9.99;
// bigint (for integers larger than Number.MAX_SAFE_INTEGER)
const huge = 9007199254740993n;
// boolean
const active = true;
// undefined (declared but no value assigned)
let score;
// null (intentionally empty)
const result = null;
// symbol (unique identifier)
const id = Symbol("userId");
Numbers and Their Quirks
JavaScript uses 64-bit floating point for all numbers, which leads to some well-known surprises:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
// Safe comparison for floating point
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // true
Useful number values to know:
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.isInteger(4.0)); // true
console.log(Number.isNaN(NaN)); // true
console.log(isFinite(1 / 0)); // false
Strings and Template Literals
Template literals (backtick strings) are the modern way to work with strings. They support interpolation and multi-line text:
const name = "Alice";
const age = 25;
// Concatenation (old way)
const msg1 = "Hello, " + name + "! You are " + age + " years old.";
// Template literal (modern way)
const msg2 = `Hello, ${name}! You are ${age} years old.`;
// Expressions work inside ${}
const msg3 = `In 5 years, you'll be ${age + 5}.`;
// Multi-line strings
const html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
Type Coercion
JavaScript automatically converts types in certain situations. This is called coercion, and it’s one of the most common sources of confusion.
// String coercion with +
console.log("5" + 3); // "53" — number becomes string
console.log("5" + true); // "5true"
// Numeric coercion with other operators
console.log("5" - 3); // 2 — string becomes number
console.log("5" * 2); // 10
console.log("abc" * 2); // NaN
// Boolean coercion
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean("hello")); // true
console.log(Boolean(42)); // true
0, "", null, undefined, NaN, and false are falsy — they evaluate to false in a boolean context. Everything else is truthy, including "0", [], and {}.
Explicit Type Conversion
Instead of relying on coercion, convert types explicitly:
// To string
String(42); // "42"
(42).toString(); // "42"
// To number
Number("42"); // 42
Number("abc"); // NaN
parseInt("42px"); // 42 — parses until it hits a non-digit
parseFloat("3.14"); // 3.14
// To boolean
Boolean(1); // true
Boolean(""); // false
Checking Types
typeof works for most cases, but has a few gotchas:
typeof "hello" // "string"
typeof 42 // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 10n // "bigint"
// The gotchas
typeof null // "object" (a bug from JavaScript's early days)
typeof [] // "object" (arrays are objects)
typeof {} // "object"
For arrays and null, use more specific checks:
Array.isArray([1, 2, 3]); // true
Array.isArray("hello"); // false
const val = null;
val === null; // true (use strict equality for null checks)
Destructuring
Destructuring lets you unpack values from arrays and objects into individual variables. It’s one of the most useful features in modern JavaScript.
Array Destructuring
const colors = ["red", "green", "blue"];
const [first, second, third] = colors;
console.log(first); // "red"
console.log(second); // "green"
// Skip values
const [, , last] = colors;
console.log(last); // "blue"
// Default values
const [a, b, c, d = "yellow"] = colors;
console.log(d); // "yellow"
// Swap variables
let x = 1;
let y = 2;
[x, y] = [y, x];
console.log(x, y); // 2, 1
Object Destructuring
const user = { name: "Alice", age: 25, role: "developer" };
const { name, age } = user;
console.log(name); // "Alice"
console.log(age); // 25
// Rename variables
const { name: userName, role: userRole } = user;
console.log(userName); // "Alice"
// Default values
const { name: n, country = "Unknown" } = user;
console.log(country); // "Unknown"
Destructuring is especially common in function parameters:
function greet({ name, role = "guest" }) {
return `Hello, ${name}! You're a ${role}.`;
}
console.log(greet({ name: "Alice", role: "admin" }));
// "Hello, Alice! You're an admin."
console.log(greet({ name: "Bob" }));
// "Hello, Bob! You're a guest."
Spread and Rest
The ... syntax serves two purposes depending on context.
Spread expands an array or object:
const nums = [1, 2, 3];
const more = [...nums, 4, 5]; // [1, 2, 3, 4, 5]
const defaults = { theme: "dark", lang: "en" };
const settings = { ...defaults, lang: "fr" }; // { theme: "dark", lang: "fr" }
Rest collects remaining items:
const [head, ...tail] = [1, 2, 3, 4];
console.log(head); // 1
console.log(tail); // [2, 3, 4]
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
What’s Next
With a solid understanding of variables, types, and modern syntax like destructuring and template literals, you’re ready to dive into functions in more depth — covering closures, higher-order functions, and how this works.