JS Fundamentals Cheat Sheet
1. var / let / const
var is function-scoped and is legacy. You can redeclare vars. Hoisting applies and when hoisted, it initalizes to undefined, so referencing it early gives you undefined instead of an error.
let and const are block-scoped, ES6 (2015). Both hoist but stay uninitialized. Redeclaring throws an error “Identifier … has already been declared”.
const binds the name, not the value. The object is still mutable under the hood.
const cfg = { debug: true };
cfg.debug = false; // fine
cfg = {}; // TypeError
2. Temporal Dead Zone
The stretch from the top of the scope down to where the variable or constant is declared. The binding exists but is uninitialized. If you use it before you declare it, it will throw a ReferenceError.
console.log(x); // ReferenceError
let x = 5; // TDZ ends here where declare & assign
var hoists the same way but gets initialized to undefined immediately, and so it will fail quietly.
3. Arrow vs regular functions
Arrows functions don’t have their own this. A regular function’s this depends on how it’s called. An arrow uses the this from where it was written.
setInterval(function () { this.count++ }, 1000); // wrong this
setInterval(() => { this.count++ }, 1000); // works
Also true of arrows:
• no arguments
• no new and so can’t be constructors
• don’t get call-time bindings
• Often used with implicit returns (no curly braces)
No curly braces means implicit return. Add braces and you need return explicitly set.
Variable scope is identical in both.
4. == vs ===
=== is strict. Same type, same value, no conversion.== coerces first, using a fixed type-pair table. Not left-wins.
1 === "1" // false
1 == "1" // true
"1" == true // true, boolean goes to number first
Objects compare by reference under both. {a:1} === {a:1} is false and so is ==. No deep-equality operator exists in the language.
The one deliberate ==: x == null catches null and undefined and nothing else.
5. Spread, rest, destructuring
... is used as either spread or rest. It is a spread when you see it on the right side of an assignment; it is a rest when you see it on the left side of an assignment or as part of the parameters of a function declaration.
const copy = { ...obj }; // spread, expands
const { a, ...rest } = obj; // rest, gathers
Spread is a shallow copy. Nested objects are shared by reference.
const copy = { ...orig };
copy.config.debug = false; // also changes orig
Deep copy: structuredClone(obj). The JSON.parse(JSON.stringify()) trick drops Date, undefined, functions, Map, Set.
Destructuring unwraps a package (object), pulls the values out, assigns them to the named variables on the left side of the assignment.
let obj = {a: "hello", b: "world"}
const { a, b } = obj; // destructuring
This works because the names of the variables on the left match the names of the properties inside the object.
6. Closures
A function that keeps access to the variables from the scope where it was defined, even after that scope has returned. Those variables stay alive because the inner function still points at them.
Used for lots of patterns:
• event handlers
• callbacks
• partial application pattern
• hooks
• storing state without needing classes
function counter() {
let count = 0;
return () => ++count;
}
const next = counter();
next(); // 1
next(); // 2
7. null vs undefined
null is intentional. A programmer assigned it to mean “empty on purpose.”
undefined is the default when nothing was assigned: a declared but unassigned variable, a missing property, a missing argument, or a function with no return.
A variable that was never declared doesn’t give undefined. It throws a ReferenceError.
typeof null is "object". That’s a historical bug in the language.
8. ?? vs ||
?? falls back only when the left side is null or undefined.|| falls back when the left side is any falsy value, including 0, "", false, and NaN.
const count = 0;
count || 10; // 10, probably a bug
count ?? 10; // 0
Use ?? for defaults when 0 or "" are valid values.
9. async / await
An async function always returns a promise, even if you return a plain value.
await pauses that function until a promise settles. The rest of the program keeps running.
async function load() {
const res = await fetch(url); // load() pauses here
return res.json();
}
await works only inside an async function or at the top level of a module.
10. Promise.all vs Promise.allSettled
Promise.all rejects as soon as any one promise rejects. The others keep running. Promises can’t be cancelled. all just ignores their results.
Promise.allSettled waits for all of them and never rejects. You get an array of { status: "fulfilled", value } or { status: "rejected", reason }.
Use allSettled when partial failure is acceptable.
11. Event loop order
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");
// A, D, C, B
Synchronous code runs first. Then the microtask queue drains, which holds promise callbacks. Then the next task runs, which holds setTimeout callbacks.
A .then never runs inline, even on an already-resolved promise.
12. Optional chaining ?.
If the thing on the left is null or undefined, the expression stops and returns undefined instead of throwing.
user?.address?.city
obj?.[key]
callback?.()
A missing property already returns undefined without ?.. The operator protects you when the object itself is missing.
13. Map vs plain object
Mapkeys can be any type. Object keys are strings or symbols.Maphas.size. Objects needObject.keys(obj).length.Mapiterates directly, in insertion order.Maphas no prototype keys, so no collisions with names likeconstructor.Mapperforms better with frequent adds and deletes.
14. reduce
Walks an array and carries an accumulator forward, ending with a single value.
[1, 2, 3].reduce((acc, cur) => acc + cur, 0); // 6
Always pass the initial value. Without it, an empty array throws.
15. Event delegation
Put one listener on a parent. Events from children bubble up to it. Check event.target to see which child fired.
list.addEventListener("click", (e) => {
if (e.target.matches("li")) handle(e.target);
});
Fewer listeners, and it works for children added later.
16. map vs forEach
map returns a new array of transformed values. forEach returns undefined and is for side effects.
Neither mutates the array on its own. Neither can be broken out of. If you need break, use for...of.
Lowercase map is the array method. Capital Map is the data structure.