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

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.