optional-chaining.js
Modern SyntaxBeginner
const user = { profile: { name: 'Ada' } };
// Old way — one missing link and it throws
// const city = user.profile.address.city; // 💥 TypeError
// Optional chaining short-circuits to undefined instead
const city = user.profile?.address?.city; // undefined
// ?? only falls back on null/undefined — 0 and '' survive
const display = city ?? 'Unknown city';
// Works on methods and array indexes too
user.sendEmail?.(); // no-op if the method doesn't exist
const first = user.friends?.[0];
Optional chaining + nullish coalescing
Why pros use it: This pair kills the single most common JavaScript crash: Cannot read properties of undefined. The subtlety pros know: ?? only falls back on null/undefined, while || also discards perfectly valid values like 0, false and '' — a classic source of silent bugs in forms and settings.
destructuring.js
Modern SyntaxBeginner
const config = { theme: 'dark', lang: 'en' };
// Pull values out, rename them, set defaults — one line
const { theme: colourScheme, retries = 3 } = config;
// In function signatures: a self-documenting API
function createUser({ name, role = 'viewer' }) {
return `${name} (${role})`;
}
createUser({ name: 'Ada' }); // 'Ada (viewer)'
// Array destructuring: swap without a temp variable
let a = 1, b = 2;
[a, b] = [b, a];
Destructuring with defaults and renaming
Why pros use it: Destructured function signatures are self-documenting — callers see exactly which fields matter, arguments become order-independent, and defaults live next to the names they belong to. This is why nearly every modern library API accepts a single options object instead of five positional arguments.
spread-rest.js
Modern SyntaxBeginner
const base = { theme: 'dark', lang: 'en' };
// Immutable update — a NEW object, original untouched
const updated = { ...base, lang: 'fr' };
// Merge and clone arrays without mutation
const merged = [...listA, ...listB];
// Rest: collect "everything else"
const { theme, ...withoutTheme } = updated;
// Variadic functions without the arguments object
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
Spread and rest: immutable updates
Why pros use it: React, Redux and most state libraries detect changes by comparing object references — mutating an object in place makes changes invisible to them. Spread creates a new object cheaply, which is why { ...state, key: value } is the backbone of modern state management. Note it is a shallow copy: nested objects are still shared.
guard-clauses.js
Modern SyntaxBeginner
// ❌ Nested: the happy path is buried three levels deep
function shipOrder(order) {
if (order) {
if (order.paid) {
if (order.items.length > 0) {
// ship it
}
}
}
}
// ✅ Guard clauses: fail fast, then read flat
function shipOrderClean(order) {
if (!order) return;
if (!order.paid) return;
if (order.items.length === 0) return;
// ship it — zero indentation, zero mental stack
}
Guard clauses over nested ifs
Why pros use it: Every level of nesting is another condition the reader must hold in their head. Guard clauses handle the failure cases immediately and exit, so the happy path reads top-to-bottom at zero indentation. Code reviewers at most companies will flag deep nesting — this is the refactor they are asking for.
console-tricks.js
Modern SyntaxBeginner
const user = { name: 'Ada', role: 'admin' };
// Wrap in braces: logs the variable NAME with its value
console.log({ user }); // { user: { name: 'Ada', … } }
// Arrays of objects become a sortable table
console.table([
{ route: '/home', visits: 1024 },
{ route: '/tools', visits: 862 }
]);
// Measure anything
console.time('render');
// …work…
console.timeEnd('render'); // render: 12.4ms
// Logs ONLY when the condition fails
console.assert(user.role === 'admin', 'Expected an admin!');
Console beyond console.log
Why pros use it: Debugging speed is a compounding skill. { user } logs the variable's name along with its value, console.table turns arrays of objects into sortable tables, and console.time gives you real measurements instead of guesses. Open this page's DevTools and try them.
last-item.js
Arrays & ObjectsBeginner
const queue = ['first', 'second', 'third'];
// Old way — repeat the array name, do the maths
const last = queue[queue.length - 1];
// Modern way — reads like English
queue.at(-1); // 'third'
queue.at(-2); // 'second'
// Works on strings too
'FrontendHelpers'.at(-1); // 's'
Grab the last item with .at(-1)
Why pros use it: arr[arr.length - 1] forces the reader to parse an expression to understand an intent. .at(-1) states the intent directly, works on strings too, and chains cleanly on temporary values — getList().at(-1) needs no intermediate variable.
dedupe.js
Arrays & ObjectsBeginner
const tags = ['css', 'js', 'css', 'html', 'js'];
// A Set can only hold unique values — spread it back out
const unique = [...new Set(tags)]; // ['css', 'js', 'html']
// Bonus: O(1) membership checks vs O(n) for .includes()
const allowedRoles = new Set(['admin', 'editor']);
allowedRoles.has('admin'); // true — instant at any size
Deduplicate with Set
Why pros use it: A Set physically cannot hold duplicates, so spreading one back into an array is the cleanest dedupe there is. The deeper win: set.has() is O(1) while array.includes() scans every element — for repeated membership checks on big lists, the difference is dramatic.
flatmap.js
Arrays & ObjectsIntermediate
const posts = [
{ title: 'Intro', tags: ['css', 'basics'] },
{ title: 'Deep dive', tags: ['js'] }
];
// map + flat in a single pass
const allTags = posts.flatMap(post => post.tags);
// ['css', 'basics', 'js']
// Trick: return [] to filter and map at the same time
const longTags = posts.flatMap(post =>
post.tags.filter(tag => tag.length > 2)
);
// ['css', 'basics']
flatMap: map and flatten in one pass
Why pros use it: When every item maps to a list, map().flat() walks the data twice and allocates an intermediate array. flatMap does it in one pass. The pro trick: returning [] for unwanted items makes it a combined filter + map — one traversal instead of two chained ones.
group-by.js
Arrays & ObjectsIntermediate
const files = [
{ name: 'logo.svg', type: 'image' },
{ name: 'app.js', type: 'script' },
{ name: 'hero.png', type: 'image' }
];
// One call, grouped buckets (baseline 2024)
const grouped = Object.groupBy(files, file => file.type);
// { image: [logo.svg, hero.png], script: [app.js] }
// Fallback for older browsers — same result
const manual = files.reduce((acc, file) => {
(acc[file.type] ??= []).push(file);
return acc;
}, {});
Group a list with Object.groupBy
Why pros use it: Grouping used to be the number-one reason to import Lodash. Object.groupBy (Chrome 117+, Firefox 119+, Safari 17.4+) does it natively in one call. Keep the reduce fallback in your toolkit for older targets — and note the tidy ??= operator that creates each bucket on first use.
deep-clone.js
Arrays & ObjectsIntermediate
const state = {
user: { name: 'Ada' },
updated: new Date(),
ids: new Set([1, 2, 3])
};
// ❌ The JSON hack corrupts silently:
// Dates → strings, Sets → {}, undefined → gone
const broken = JSON.parse(JSON.stringify(state));
// ✅ True deep clone, native since 2022
const copy = structuredClone(state);
copy.user.name = 'Grace';
state.user.name; // still 'Ada' — fully independent
Deep clone with structuredClone
Why pros use it: The old JSON.parse(JSON.stringify(obj)) hack silently corrupts data: Dates become strings, Map/Set become empty objects, undefined fields vanish. structuredClone is built into every modern browser, handles all of those plus circular references, and is usually faster too.
reduce.js
Arrays & ObjectsIntermediate
const orders = [
{ id: 'a1', total: 40 },
{ id: 'b2', total: 25 },
{ id: 'c3', total: 60 }
];
// Index by id → O(1) lookups instead of .find() everywhere
const byId = orders.reduce((acc, order) => {
acc[order.id] = order;
return acc;
}, {});
byId['b2']; // { id: 'b2', total: 25 } — instantly
// Aggregate in the same shape
const revenue = orders.reduce((sum, o) => sum + o.total, 0);
reduce: shape data, don’t just sum it
Why pros use it: The pattern to internalise is indexing by id: turn an array into a lookup object once, then every subsequent access is O(1) instead of an O(n) .find(). On a list you query hundreds of times — rows by id, users by email — this is one of the highest-value micro-optimisations there is, and it makes the code clearer too.
ranges.js
Arrays & ObjectsIntermediate
// A number range, no loop in sight
const range = Array.from({ length: 5 }, (_, i) => i + 1);
// [1, 2, 3, 4, 5]
// Practical: skeleton placeholders while data loads
const slots = Array.from({ length: 6 }, (_, i) =>
`<div class="skeleton" id="slot-${i}"></div>`
);
// Also converts any iterable into a real array
Array.from(document.querySelectorAll('a'),
link => link.href);
Generate ranges with Array.from
Why pros use it: JavaScript has no built-in range() — this idiom is the standard substitute. The { length: n } object is enough for Array.from to iterate, and the mapping function receives the index. You will use it constantly: pagination buttons, skeleton placeholders, calendar grids, test fixtures.
all-settled.js
Async PatternsIntermediate
const results = await Promise.allSettled([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/flaky-service')
]);
results.forEach(result => {
if (result.status === 'fulfilled') {
render(result.value);
} else {
logError(result.reason);
// …the OTHER two requests still succeeded
}
});
// Promise.all would have rejected everything here
Promise.allSettled: no all-or-nothing
Why pros use it: Promise.all rejects the moment one promise fails — the other results are thrown away. That is right for dependent data, but wrong for dashboards, widget grids and batch jobs where partial success is useful. allSettled always resolves, handing you every outcome to handle individually.
fetch-timeout.js
Async PatternsPro
async function fetchWithTimeout(url, ms = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), ms);
try {
const response = await fetch(url, {
signal: controller.signal
});
return await response.json();
} finally {
clearTimeout(timer);
}
}
// Same primitive, different job: cancel the PREVIOUS
// search when the user types again — no stale results
Fetch with a timeout via AbortController
Why pros use it: fetch has no timeout by default — a hung server can leave your UI spinning forever. AbortController is the platform's cancellation primitive: the same pattern also cancels stale requests in type-ahead search, so a slow response for “ca” can never overwrite results for “cats”.
parallel-await.js
Async PatternsIntermediate
// ❌ Sequential — each await blocks the next (≈600ms)
const user = await fetchUser(); // 200ms
const posts = await fetchPosts(); // 200ms
const tags = await fetchTags(); // 200ms
// ✅ Parallel — all three start immediately (≈200ms)
const [u, p, t] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchTags()
]);
// Rule: if B doesn't need A's result, don't await A first
Parallel await beats sequential await
Why pros use it: Three sequential awaits on 200ms requests take 600ms; in parallel they take 200ms. This is the most common real-world async performance bug — the awaits look identical, but the sequential version quietly serialises independent work. Press Run to measure the difference live in your browser.
safe-await.js
Async PatternsPro
// Wrap any promise into [value, error] — never throws
const attempt = promise =>
promise
.then(value => [value, null])
.catch(error => [null, error]);
const [user, error] = await attempt(fetchUser());
if (error) {
showToast('Could not load your profile');
} else {
render(user);
}
// Flat, explicit, impossible to forget the error case
Go-style [value, error] tuples
Why pros use it: try/catch around every await pushes the happy path into nested blocks and makes it easy to forget error handling entirely. Wrapping once turns every async call into a flat, explicit two-value check — you can see at the call site that the error case was considered. Popularised by Go, now common in production JS codebases.
delegation.js
DOM & BrowserIntermediate
// ❌ 500 rows = 500 listeners — and rows added
// later have no listener at all
// ✅ ONE listener on the parent catches every click
document.querySelector('#table').addEventListener('click', e => {
const btn = e.target.closest('[data-action="delete"]');
if (!btn) return;
removeRow(btn.closest('tr'));
});
// Rows added tomorrow work automatically —
// their clicks bubble up to the same parent
Event delegation: one listener, infinite rows
Why pros use it: Events bubble up the DOM, so one parent listener hears every child click — including elements added after the listener was attached. That solves the two classic bugs at once: memory bloat from hundreds of listeners, and dynamically added rows that mysteriously do nothing. closest() finds which actionable element was actually hit.
observer.js
DOM & BrowserIntermediate
// Fires only when elements enter the viewport —
// no scroll listeners, no layout thrash
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target); // reveal once
}
});
}, { threshold: 0.2 });
document.querySelectorAll('.reveal')
.forEach(el => observer.observe(el));
// CSS does the rest:
// .reveal { opacity: 0; transform: translateY(20px); }
// .reveal.is-visible { opacity: 1; transform: none; }
IntersectionObserver for scroll effects
Why pros use it: Scroll listeners fire hundreds of times per second on the main thread, and reading element positions inside them forces synchronous layout — the recipe for scroll jank. IntersectionObserver does the visibility maths off the main thread and calls you only when it matters. The animation gallery on this site uses exactly this to replay cards as you scroll.
dataset.js
DOM & BrowserBeginner
// <button data-product-id="42" data-in-stock="true">
const button = document.querySelector('button');
button.dataset.productId; // '42' (kebab-case ↔ camelCase)
button.dataset.inStock; // 'true' — always a STRING
// Write state back into the DOM
button.dataset.state = 'loading';
// …and let CSS react with zero extra JS:
// button[data-state="loading"] { opacity: 0.5; }
data-* attributes: state the DOM can see
Why pros use it: Storing state in data-* attributes means CSS can react to it without a single line of style-juggling JavaScript — button[data-state="loading"] is a selector. One write updates both behaviour and appearance. Remember values always come back as strings: dataset.count of “3” needs a Number().
match-media.js
DOM & BrowserIntermediate
const mobile = window.matchMedia('(max-width: 768px)');
// Read once…
if (mobile.matches) enableSwipeGestures();
// …or react exactly when the breakpoint flips
mobile.addEventListener('change', e => {
e.matches ? enableSwipeGestures() : enableHoverMenus();
});
// Any media feature works, not just width
const reducedMotion =
window.matchMedia('(prefers-reduced-motion: reduce)');
if (reducedMotion.matches) skipAnimations();
matchMedia: media queries in JavaScript
Why pros use it: A resize listener fires continuously while dragging a window; matchMedia's change event fires exactly once, at the moment the query flips. It also reads user preferences CSS can see but JS logic often ignores — reduced motion, colour scheme, pointer type. Press Run to query your actual browser state.
clipboard.js
DOM & BrowserBeginner
async function copy(text) {
try {
await navigator.clipboard.writeText(text);
showToast('Copied!');
} catch {
// Needs HTTPS + a user gesture — offer a fallback
showToast('Press Ctrl+C to copy');
}
}
// Reading is gated harder (permission prompt):
const pasted = await navigator.clipboard.readText();
Clipboard API done properly
Why pros use it: The async Clipboard API replaced the old document.execCommand('copy') hack, but it has two conditions juniors miss: it needs a secure context (HTTPS or localhost) and a user gesture. Wrap it in try/catch with a human fallback and it degrades gracefully — every Copy button on this site uses exactly this pattern.
storage.js
DOM & BrowserBeginner
const storage = {
get(key, fallback = null) {
try {
const raw = localStorage.getItem(key);
return raw === null ? fallback : JSON.parse(raw);
} catch {
return fallback; // corrupt JSON or blocked storage
}
},
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch { /* quota full / private mode — stay calm */ }
}
};
storage.set('prefs', { theme: 'dark', fontSize: 18 });
storage.get('prefs', {}); // a real object, not a string
A safe localStorage wrapper
Why pros use it: Raw localStorage has three traps: it only stores strings (objects become "[object Object]"), getItem returns null that then crashes JSON.parse chains, and in some private-browsing modes setItem throws. Ten lines of wrapper remove all three permanently.
debounce.js
PerformanceIntermediate
function debounce(fn, delay = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// 5 keystrokes → ONE request, 300ms after typing stops
const search = debounce(query => {
fetch('/api/search?q=' + encodeURIComponent(query));
}, 300);
input.addEventListener('input', e => search(e.target.value));
Debounce: wait until they stop
Why pros use it: Typing “hello” fires five input events; without debouncing that is five API calls, four of them wasted. Debounce collapses the burst into one call after the user pauses. The trade-off is latency — results arrive delay ms after the last keystroke — so tune it: ~300ms for search, ~500ms for autosave.
raf-throttle.js
PerformancePro
function rafThrottle(fn) {
let scheduled = false;
return (...args) => {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
fn(...args);
scheduled = false;
});
};
}
// Runs at most once per frame — never wasted work
window.addEventListener('scroll',
rafThrottle(() => updateReadingProgress()),
{ passive: true });
Throttle to the frame rate with rAF
Why pros use it: Scroll and pointer events can fire far more often than the screen repaints — any work beyond once per frame is thrown away, yet still burns CPU. requestAnimationFrame throttling runs your handler at most once per paint, perfectly synced to the display. Prefer this over setTimeout throttles for anything visual.
memoize.js
PerformancePro
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (!cache.has(key)) {
cache.set(key, fn(...args));
}
return cache.get(key);
};
}
// Only PURE functions qualify:
// same input → same output, no side effects
const layout = memoize(computeExpensiveLayout);
Memoization: never compute twice
Why pros use it: If a function is pure — same input, same output — recomputing it is pure waste. A Map cache trades memory for speed. Press Run: naive fib(20) makes ~22,000 calls; memoised, 21. The same idea powers React's useMemo and every build-tool cache you have ever waited for.
intl-format.js
Utilities & IntlBeginner
// Currency, correctly, zero dependencies
new Intl.NumberFormat('en-GB', {
style: 'currency', currency: 'GBP'
}).format(1499.99); // £1,499.99
// Compact notation for dashboards and counters
new Intl.NumberFormat('en', {
notation: 'compact'
}).format(1250000); // 1.3M
// Dates that respect the reader's locale
new Intl.DateTimeFormat('en-GB', {
dateStyle: 'long'
}).format(new Date()); // 17 July 2026
Intl: currency and dates without libraries
Why pros use it: Hand-rolled price formatting with toFixed(2) breaks the moment you meet thousands separators, other currencies or other locales. Intl ships with the browser, knows every locale's rules, and replaces entire formatting libraries at zero bundle cost. This is the modern answer to “should I install a date library?” — usually, no.
time-ago.js
Utilities & IntlIntermediate
const rtf = new Intl.RelativeTimeFormat('en', {
numeric: 'auto'
});
rtf.format(-1, 'day'); // 'yesterday'
rtf.format(-3, 'hour'); // '3 hours ago'
rtf.format(2, 'week'); // 'in 2 weeks'
// Pick the right unit automatically
function timeAgo(date) {
const s = (date - Date.now()) / 1000;
const units = [['year', 31536000], ['month', 2592000],
['day', 86400], ['hour', 3600], ['minute', 60]];
for (const [unit, size] of units) {
if (Math.abs(s) >= size)
return rtf.format(Math.round(s / size), unit);
}
return 'just now';
}
“3 hours ago” with Intl.RelativeTimeFormat
Why pros use it: Relative timestamps were the last big reason teams kept Moment.js (and its 70KB) around. Intl.RelativeTimeFormat handles the grammar — including special forms like “yesterday” via numeric: 'auto' — in every locale, natively. The timeAgo helper below picks the sensible unit for any date.
uuid.js
Utilities & IntlBeginner
// Cryptographically strong, collision-safe, built in
const id = crypto.randomUUID();
// '36b8f84d-df4e-4d49-b662-bcde71a8764f'
// ❌ The old hack — collisions are a real risk:
const risky = Math.random().toString(36).slice(2);
// Need raw random bytes instead?
const bytes = crypto.getRandomValues(new Uint8Array(8));
Real unique IDs with crypto.randomUUID
Why pros use it: Math.random() is not collision-safe — with enough generated IDs, duplicates will happen, and the resulting bugs (two list rows sharing a key, overwritten records) are miserable to trace. crypto.randomUUID() produces cryptographically strong v4 UUIDs natively. One caveat: it requires a secure context (HTTPS or localhost).
event-loop.js
Async PatternsPro
console.log('1: synchronous');
setTimeout(() => {
console.log('4: macrotask (setTimeout 0)');
}, 0);
Promise.resolve().then(() => {
console.log('3: microtask (promise .then)');
});
console.log('2: synchronous again');
// Order: 1, 2, 3, 4 — ALL microtasks run before
// the next macrotask, no matter the timeout value
The event loop: microtasks vs macrotasks
Why pros use it: This is the interview question that separates "uses async" from "understands async". After every synchronous chunk finishes, JavaScript drains the entire microtask queue (promise callbacks, queueMicrotask) before running even one macrotask (setTimeout, events). That's why setTimeout(fn, 0) is never really 0 — and why a flood of chained promises can starve rendering. Press Run: the real order, from your browser.
proxy-state.js
Modern SyntaxPro
function reactive(target, onChange) {
return new Proxy(target, {
set(obj, key, value) {
const old = obj[key];
obj[key] = value;
if (old !== value) onChange(key, value);
return true; // signal success (strict mode cares)
}
});
}
const state = reactive({ count: 0 }, (key, value) => {
render(`${key} changed to ${value}`);
});
state.count = 1; // render() fires automatically
state.count = 2; // and again — no framework involved
Proxy: reactive state in 15 lines
Why pros use it: A Proxy intercepts property access on an object — which is exactly how Vue's reactivity and many state libraries work under the hood. The set trap fires on every assignment, so "change state, UI updates itself" needs no framework at all. The trade-off: proxies intercept the wrapper, so state.user.name = x on nested objects needs recursive wrapping — which is precisely what reactivity libraries sell you.
generators.js
Modern SyntaxPro
function* fibonacci() {
let [a, b] = [0, 1];
while (true) { // infinite — and totally safe
yield a;
[a, b] = [b, a + b];
}
}
// take(n): consume just n values from any generator
function take(gen, n) {
const out = [];
for (const value of gen) {
out.push(value);
if (out.length === n) break;
}
return out;
}
take(fibonacci(), 8); // [0, 1, 1, 2, 3, 5, 8, 13]
Generators: lazy, even infinite, sequences
Why pros use it: A generator produces values on demand — it pauses at every yield until someone asks for the next value. That makes "infinite" sequences safe: nothing is computed until consumed. Pros reach for them for ID factories, pagination cursors, and processing huge datasets without building huge arrays. function* looks alien for a week, then becomes a superpower.
pipe.js
Utilities & IntlIntermediate
const pipe = (...fns) =>
(input) => fns.reduce((value, fn) => fn(value), input);
// Small, single-purpose, individually testable steps
const trim = s => s.trim();
const lower = s => s.toLowerCase();
const hyphenate = s => s.replace(/\s+/g, '-');
const slugify = pipe(trim, lower, hyphenate);
slugify(' Hello Wonderful World ');
// 'hello-wonderful-world'
// Reads top-to-bottom like a recipe — compare:
// hyphenate(lower(trim(input))) ← inside-out
Pipe: composing small functions
Why pros use it: Deeply nested calls read inside-out: c(b(a(x))) makes the reader start in the middle. pipe flips that into reading order — data flows left to right through a list of small, testable, reusable steps. This is the functional style underneath RxJS, lodash/fp and most data pipelines, in three lines of vanilla JS.
No snippets match your search. Try a broader term like “array”, “async” or “format”.
Why “modern” JavaScript matters
Most of the snippets on this page replace patterns that needed a utility library five years ago. Deep cloning used to mean importing Lodash; formatting a date meant Moment.js; generating an ID meant a UUID package. Today structuredClone, Intl and crypto.randomUUID are built into every modern browser — shipping a library to do the same work just adds bundle weight, slows your page and widens your dependency attack surface.
Knowing what the platform now does natively is one of the clearest markers separating junior from senior frontend work. The platform team at every browser vendor has spent a decade absorbing the best ideas from userland libraries — the modern developer's job is to know they landed.
Copy less, understand more
Every snippet here is deliberately short enough to read in one sitting, and each one names the trade-off it makes. A debounce delays work at the cost of latency; event delegation saves memory at the cost of one extra DOM lookup per click; Promise.all is fast but fails as a group. Understanding the trade-off is what lets you decide when not to use a pattern — which is the skill interviews actually probe for.
All code on this page is dependency-free, framework-agnostic and safe to paste into any project — vanilla JS, React, Vue or Svelte alike. No attribution needed.
How these snippets are chosen
The 32 patterns here aren't a random grab-bag — they're the ones that come up repeatedly in code reviews, interview questions and real production debugging. Each one had to clear three bars: it must replace a pattern that is either longer, more error-prone or more dependency-heavy; it must work in every modern browser without a transpiler; and the explanation must fit in a paragraph. If a trick is genuinely useful but takes a wall of text to explain safely, it belongs in a guide, not a snippet.
New snippets are added as the JavaScript platform evolves — when a proposal moves to Stage 4 and browsers ship it, the collection grows. Object.groupBy, Array.fromAsync and the using keyword for resource disposal are the most recent additions. Patterns that become so mainstream they stop needing explanation are retired to keep the collection useful rather than encyclopaedic.