Cheatsheets

📋 NPBlue Cheatsheets 11 sheets

Condensed, scannable reference cards — commands, flags, and syntax you can copy-paste mid-task, distilled from NPBlue's full guides.

JavaScript Cheatsheet

The array/object methods, the async patterns, and the scoping rules that actually cause bugs in production. For the full explanations, see the JavaScript guides.

The event loop, in one picture

stack empties

microtask queue fully drained

one task, then re-check

Call Stack

(synchronous code runs here, one frame at a time)

Microtask Queue

(Promise .then/.catch, queueMicrotask)

Task Queue

(setTimeout, setInterval, I/O, DOM events)

The rule that explains almost every “why did this run in this order” question: after the call stack empties, JavaScript drains the entire microtask queue before touching a single macrotask. This is why Promise.resolve().then(...) always runs before a setTimeout(fn, 0), even though the timeout has “zero” delay — the microtask queue has priority, every time, no exceptions.

console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// Output: 1, 4, 3, 2 — sync code first, then all microtasks, then macrotasks

var, let, and const

function example() {
if (true) {
var x = 1; // function-scoped — leaks out of the if-block
let y = 2; // block-scoped — only exists inside { }
const z = 3; // block-scoped, reassignment forbidden
}
console.log(x); // 1 — works, which surprises people
console.log(y); // ReferenceError — y doesn't exist here
}

var being function-scoped instead of block-scoped is the source of the classic closure-in-a-loop bug:

// WRONG — logs 3, 3, 3 because var i is shared across all three closures
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// RIGHT — let creates a fresh binding per iteration
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}

const prevents reassignment of the variable binding, not mutation of what it points to — const arr = [1,2,3]; arr.push(4) is completely legal because arr still points at the same array; only arr = [5,6,7] would throw.

Destructuring and spread

const { name, age = 18, ...rest } = { name: "Alice", city: "NYC", country: "US" };
// name = "Alice", age = 18 (default, not in object), rest = { city: "NYC", country: "US" }
const [first, second, ...others] = [1, 2, 3, 4, 5];
// first = 1, second = 2, others = [3, 4, 5]
function greet({ name, greeting = "Hello" } = {}) {
return `${greeting}, ${name}!`;
}
const merged = { ...defaults, ...overrides }; // overrides wins on key collisions
const combined = [...arr1, ...arr2]; // concatenate without .concat()

The = {} default in the greet example matters: without it, calling greet() with no argument tries to destructure undefined and throws immediately, before the individual field defaults ever get a chance to apply.

Array methods — pick the right one

MethodReturnsUse it for
.map()New array, same lengthTransforming every element
.filter()New array, subsetKeeping elements that match a condition
.reduce()Any single valueFolding an array down to one thing (sum, object, another array)
.forEach()undefinedSide effects only — can’t break out of it, can’t return a value
.find()First matching element or undefinedStop-on-first-match search
.some() / .every()Boolean”does at least one” / “do all” match
.flatMap()Flattened arrayMap, then flatten one level — avoids a separate .flat() call
const orders = [{ amount: 50 }, { amount: 120 }, { amount: 30 }];
const total = orders.reduce((sum, o) => sum + o.amount, 0); // 200
const highValue = orders.filter(o => o.amount > 40).map(o => o.amount); // [50, 120]
const hasLarge = orders.some(o => o.amount > 100); // true
// reduce building an object — the pattern that replaces a manual for-loop + object
const byTier = orders.reduce((acc, o) => {
const tier = o.amount > 100 ? "high" : "low";
(acc[tier] ??= []).push(o);
return acc;
}, {});

.forEach() cannot be stopped early (no break equivalent) and its return value is always undefined — reaching for .find() or a plain for...of loop (which does support break) is the fix when you catch yourself trying to return out of a .forEach() callback to “stop” it.

Promises and async/await

function fetchUser(id) {
return fetch(`/api/users/${id}`)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
});
}
// async/await — same thing, reads top-to-bottom instead of chained
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// Running requests in parallel — the mistake that halves nobody's load time until it's fixed
// SLOW — each await blocks the next call from starting
const user = await fetchUser(1);
const posts = await fetchPosts(1);
// FAST — both requests fire immediately, only the waiting happens together
const [user, posts] = await Promise.all([fetchUser(1), fetchPosts(1)]);
// Promise.allSettled — when one failure shouldn't cancel the rest
const results = await Promise.allSettled([fetchUser(1), fetchUser(999)]);
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.error(r.reason);
});

Promise.all rejects as soon as any promise rejects, discarding the results of the ones that succeeded — Promise.allSettled waits for every promise to finish regardless of outcome and gives you a status per result, which is the right tool whenever partial success is meaningful (batch API calls where one bad ID shouldn’t blank out the other nine responses).

// Error handling with async/await — needs try/catch, unlike a .then() chain's .catch()
async function safeFetch(id) {
try {
return await fetchUser(id);
} catch (err) {
console.error("Fetch failed:", err.message);
return null;
}
}

Closures

function makeCounter() {
let count = 0;
return {
increment: () => ++count,
reset: () => { count = 0; },
};
}
const counter = makeCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.reset();

A closure is a function that remembers the variables from the scope it was created in, even after that outer function has finished running — count here isn’t a global or a class field, it’s a variable that only the returned functions can see and modify, which is the mechanism behind private state in plain JavaScript (no class, no #private fields needed).

this — the rule that actually matters

const obj = {
name: "Widget",
regular() { return this.name; }, // regular function — `this` is dynamic
arrow: () => { return this.name; }, // arrow — `this` is lexical (inherited from surrounding scope)
};
obj.regular(); // "Widget" — called as obj.regular(), so `this` is obj
const fn = obj.regular;
fn(); // undefined (or throws in strict mode) — `this` depends on HOW it's called, not where it's defined
obj.arrow(); // whatever `this` was where obj was defined (often undefined/window) — NOT obj

Regular functions determine this from the call site (how the function was invoked), which is why detaching a method from its object (const fn = obj.method) and calling it separately silently breaks this. Arrow functions ignore the call site entirely and inherit this from their enclosing lexical scope at definition time — which is exactly why arrow functions are the standard choice for callbacks inside a class method (setTimeout(() => this.update(), 100)), where you want this to keep meaning the class instance, not whatever called the callback.

Optional chaining and nullish coalescing

const city = user?.address?.city; // undefined instead of throwing, if address is missing
const port = config.port ?? 8080; // 8080 only if port is null/undefined — NOT if it's 0 or ""
const portWrong = config.port || 8080; // bug: replaces 0 or "" too, since both are falsy
user?.notify?.(); // optional call — only invokes if notify exists

?? differs from || specifically around falsy-but-valid values: 0, "", and false are legitimate values that || would incorrectly override with the fallback, while ?? only falls back on null/undefined. A config port of 0 or an empty allowed string are the concrete cases where || silently produces the wrong result and ?? is the actual fix.

== vs === and type coercion

0 == false // true — coerced
0 === false // false — different types, no coercion
"" == 0 // true — both coerced to 0
null == undefined // true — special-cased to equal each other
null === undefined // false
NaN === NaN // false — NaN is never equal to anything, including itself
Number.isNaN(NaN) // true — the correct way to check for NaN

== triggers type coercion following a specific, easy-to-misremember set of rules before comparing; === never coerces and compares type and value directly. The practical rule: use === by default, everywhere, and only reach for == in the one case people actually rely on it for — checking x == null as shorthand for “null or undefined.”

Classes and prototypal inheritance

class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
speak() {
return `${super.speak()}, specifically a bark`;
}
}
new Dog("Rex").speak(); // "Rex makes a sound, specifically a bark"

class syntax is sugar over JavaScript’s actual inheritance mechanism — the prototype chain. Every object has an internal link to another object (its prototype), and property lookups walk up that chain until they find a match or hit null. class/extends didn’t add a new inheritance model to the language; they made the existing prototype-chain model readable without manually wiring up Dog.prototype = Object.create(Animal.prototype), which is what this code compiled down to before ES6.

Common patterns

Debounce — collapse rapid-fire events into one

function debounce(fn, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
const debouncedSearch = debounce((query) => fetchResults(query), 300);
input.addEventListener("input", (e) => debouncedSearch(e.target.value));

Every keystroke resets the timer — the search only actually fires once the user pauses for 300ms, instead of firing (and hitting the API) on every single keystroke.

Throttle — cap the rate, don’t collapse to the end

function throttle(fn, limit) {
let waiting = false;
return (...args) => {
if (waiting) return;
fn(...args);
waiting = true;
setTimeout(() => { waiting = false; }, limit);
};
}
window.addEventListener("scroll", throttle(() => updateScrollPosition(), 100));

Debounce waits for silence; throttle guarantees a maximum firing rate regardless of how continuous the events are — scroll/resize handlers want throttle (you need periodic updates during the activity), search-as-you-type wants debounce (you only care about the final value).

Deep cloning without a library

const clone = structuredClone(original); // built into modern browsers/Node 17+, handles dates, Maps, Sets
// Older environments: JSON.parse(JSON.stringify(original)) — but drops functions, undefined, and Dates become strings

Memoizing an expensive pure function

function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}

Map and Set — when a plain object isn’t the right tool

const seen = new Set();
const uniqueItems = items.filter(item => {
if (seen.has(item.id)) return false;
seen.add(item.id);
return true;
});
const cache = new Map();
cache.set(userObject, "metadata"); // Map keys can be objects — plain {} keys are always coerced to strings
cache.get(userObject);
for (const [key, value] of cache) { ... } // directly iterable, unlike plain objects

A plain object silently stringifies any non-string key (obj[someObject] becomes obj["[object Object]"]), which breaks the moment two different objects get used as keys. Map keeps keys as their actual reference/type, and Set gives you O(1) uniqueness checks without building a lookup object by hand — both are the correct default over a plain object/array for these specific jobs, not just a stylistic preference.


Not covered: The DOM APIs, module bundler configuration, and TypeScript’s type system get their own dedicated coverage rather than a footnote here — this sheet is scoped to core language behavior. For deeper coverage, see the JavaScript guides.