Q1 of 40 · JavaScript
What is the difference between let, const, and var?
Short answer
Short answer: var is function-scoped and hoisted (initialised to undefined before its declaration line runs); let and const are block-scoped and throw a ReferenceError if accessed before their declaration (temporal dead zone). Use const by default, let when reassignment is needed, and avoid var in modern code.
Detail
var predates block scoping in JavaScript. Declarations are hoisted to the top of their containing function (or global scope), which means you can reference a var before its line — it just evaluates to undefined until the assignment runs. This makes control-flow bugs hard to spot because the code doesn't fail, it just behaves silently wrong.
let and const are block-scoped (enclosed by the nearest {}). They're still hoisted technically, but they're in the temporal dead zone (TDZ) until execution reaches the declaration — accessing them before that point throws a ReferenceError instead of silently returning undefined. This fail-fast behaviour makes bugs visible immediately.
The difference between let and const is rebinding: const prevents the variable binding from pointing to a different value after initialisation. It does not make objects or arrays immutable — you can still mutate properties or push to an array declared with const. For true immutability, use Object.freeze().
In test automation code: use const for anything you reference but don't rebind (const page = await browser.newPage(), fixture objects, expected values). Use let for loop counters or variables that genuinely need reassignment. var should not appear in modern test code; ESLint's no-var rule enforces this.
// EXAMPLE
scoping.js
// var: function-scoped, hoisted, leaks out of blocks
function badExample() {
console.log(x); // undefined — not ReferenceError (hoisting)
var x = 5;
if (true) {
var x = 10; // same binding — overwrites!
}
console.log(x); // 10 — leaked out of the if block
}
// const / let: block-scoped, temporal dead zone
function goodExample() {
// console.log(y); // ReferenceError — TDZ
const y = 5;
if (true) {
let z = 10; // scoped to this block only
console.log(z); // 10
}
// console.log(z); // ReferenceError — z not in scope
}
// const ≠ immutable object
const steps = [];
steps.push("login"); // fine — the array is mutable
// steps = []; // TypeError — binding is const// WHAT INTERVIEWERS LOOK FOR
// COMMON PITFALL
// Related questions