---
title: "JavaScript Tips: 32 Modern Snippets Explained"
description: "32 modern JavaScript tricks with the reasoning behind them: optional chaining, structuredClone, event delegation, debounce, AbortController, and more."
url: https://frontendhelpers.com/js-tips
updated: 2026-10-12
---

# JavaScript tricks pros actually use — and why

Every snippet below explains the reasoning, not just the syntax: what problem it solves, what it replaces, and where it bites. Several run right on this page — press _Run_ and watch the output.

### 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.

```js
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];
```

### 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.

```js
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];
```

### 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.

```js
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);
}
```

### 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.

```js
// ❌ 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
}
```

### 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.

```js
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!');
```

### 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.

```js
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'
```

### 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.

```js
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
```

### 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.

```js
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']
```

### 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.

```js
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;
}, {});
```

### 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.

```js
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
```

### 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.

```js
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);
```

### 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.

```js
// 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);
```

### 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.

```js
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
```

### 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”.

```js
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
```

### 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.

```js
// ❌ 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
```

### 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.

```js
// 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
```

### 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.

```js
// ❌ 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
```

### 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.

```js
// 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; }
```

### 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()`.

```js
// <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; }
```

### 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.

```js
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();
```

### 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.

```js
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();
```

### 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.

```js
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
```

### 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.

```js
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));
```

### 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.

```js
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 });
```

### 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.

```js
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);
```

### 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.

```js
// 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
```

### “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.

```js
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';
}
```

### 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).

```js
// 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));
```

### 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.

```js
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
```

### 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.

```js
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
```

### 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.

```js
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]
```

### 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.

```js
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
```

## 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.

## Frequently asked questions

### Can I use these JavaScript snippets in my own projects?

Yes — every snippet is free for personal and commercial use with no attribution. They're all dependency-free vanilla JavaScript, so they paste cleanly into any project, framework or build setup.

### Which browsers do these modern JavaScript features support?

Everything on the page works in current Chrome, Edge, Firefox and Safari. Where a feature is newer — like `Object.groupBy` — the snippet says so and includes a fallback for older targets.

### Why vanilla JavaScript instead of a framework or Lodash?

Because the platform caught up: `structuredClone`, `Intl`, `crypto.randomUUID` and friends now do natively what once needed libraries. Knowing the built-ins means smaller bundles, fewer dependencies to audit, and skills that transfer to every codebase.
