What you'll learn
Quick Answer
Use const and let instead of var, arrow functions for callbacks, destructuring to pull values out of objects and arrays, spread to copy and merge, and optional chaining with nullish coalescing to handle missing values safely.
let and const, and the loop bug var causes
The clearest demonstration of why var was replaced:
var fns = [];
for (var i = 0; i < 3; i++) fns.push(() => i);
console.log(fns.map(f => f())); // [3, 3, 3]
let fns2 = [];
for (let j = 0; j < 3; j++) fns2.push(() => j);
console.log(fns2.map(f => f())); // [0, 1, 2]
var is function-scoped, so all three closures capture the same i, which is 3 by the time they run. let is block-scoped and creates a fresh binding each iteration, so each closure captures its own value.
This is a classic interview question and a real bug that used to appear constantly in loops attaching event handlers. Default to const, use let when you must reassign, and treat var as legacy.
Note that const prevents reassignment, not mutation — const arr = [1,2]; arr.push(3); is legal, because the binding still points at the same array.
Destructuring
const [a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a, b, rest); // 1 2 [3, 4, 5]
const user = { name: "Asha", city: "Pune" };
const { name, city: town } = user;
console.log(name, town); // Asha Pune
Arrays destructure by position, objects by property name. city: town renames on the way out, which is useful when the incoming name would clash with something you already have.
It is used most in function parameters, where it documents what the function needs:
function greet({ name, city = "unknown" }) {
return `${name} from ${city}`;
}
A caller reading that signature immediately knows which fields matter, and city has a default if absent.
Spread and rest
const user = { name: "Asha", city: "Pune" };
const merged = { ...user, marks: 91 };
console.log(JSON.stringify(merged));
// {"name":"Asha","city":"Pune","marks":91}
Three dots mean "expand this here". It copies arrays and objects, merges them, and passes arrays as separate arguments. Later keys win, so { ...defaults, ...overrides } is the standard way to apply options.
One caveat worth knowing: this is a shallow copy. Nested objects are still shared between the copy and the original, so mutating a nested property changes both. For genuinely independent copies use structuredClone(obj).
The same syntax in a parameter list collects arguments instead: function sum(...nums) gives you a real array, unlike the old arguments object.
Arrow functions and template literals
const greet = (n) => `Hello ${n}`;
console.log(greet("Ravi")); // Hello Ravi
console.log(((x = 10) => x * 2)()); // 20
const arr = [1, 2, 3];
console.log(arr.map(n => n * 2)); // [2, 4, 6]
console.log(arr.reduce((s, n) => s + n, 0)); // 6
Backticks allow interpolation with ${...} and real multi-line strings, which removes most string concatenation.
Arrow functions are shorter, and they differ from regular functions in one important way: they do not have their own this. They inherit it from the surrounding scope, which is exactly what you want in a callback inside a class method — and exactly what you do not want for an object method that needs to refer to the object itself.
Optional chaining and nullish coalescing
These two fix real bugs rather than just shortening code.
const user = { name: "Asha", city: "Pune" };
console.log(user?.address?.pin); // undefined, no crash
Without ?., reading user.address.pin when address is missing throws "Cannot read properties of undefined" — one of the most common errors in JavaScript. Optional chaining stops and returns undefined instead.
console.log(0 ?? 99); // 0
console.log(0 || 99); // 99
This distinction matters. || falls back for any falsy value, so a legitimate 0, empty string or false gets replaced by the default. ?? falls back only for null and undefined. A quantity of zero or a stored setting of false being silently overwritten is a genuinely nasty bug, and ?? prevents it.
While on surprising behaviour: 0 == "0" is true but 0 === "0" is false, and typeof null is "object". Use === always.
