10 Essential JavaScript Tips and Tricks

10-Essential-JavaScript-Tips-and-Tricks

Over the years, I’ve collected a handful of JavaScript techniques that I now use so often they’ve become second nature. Some of these took me embarrassingly long to discover, and I want to save you that time. This isn’t a list of obscure party tricks — every single one of these is something I genuinely use in production code on a regular basis. Let’s get into it.

1. Use Optional Chaining and Nullish Coalescing Together

const user = { profile: { address: null } };

const city = user?.profile?.address?.city ?? "Unknown";
console.log(city); // "Unknown"

Optional chaining (?.) safely stops evaluating and returns undefined the moment it hits a null/undefined link in the chain, instead of throwing a TypeError. Nullish coalescing (??) then lets me provide a fallback specifically for null/undefined, without accidentally overriding legitimate falsy values like 0 or false the way || would.

const settings = { volume: 0 };
console.log(settings.volume ?? 50); // 0 — correct
console.log(settings.volume || 50); // 50 — WRONG, treats 0 as "missing"

2. Destructure with Default Values and Renaming in One Line

function renderCard({ title, subtitle: description = "No description", views: viewCount = 0 } = {}) {
  return `${title} — ${description} (${viewCount} views)`;
}

console.log(renderCard({ title: "My Post" }));
// My Post — No description (0 views)

Combining renaming (subtitle: description) and defaults (= "No description") in a single destructuring pattern makes function signatures self-documenting and eliminates a lot of boilerplate const x = obj.x || default lines.

3. Convert Array-Likes to Real Arrays Instantly

function sumArguments() {
  return [...arguments].reduce((a, b) => a + b, 0);
}
console.log(sumArguments(1, 2, 3)); // 6

const divs = [...document.querySelectorAll("div")];
divs.forEach((div) => console.log(div.textContent));

Spreading turns any iterable array-like object into a genuine array with full access to .map(), .filter(), .reduce(), and every other array method — this trips people up constantly with NodeList and arguments, which look like arrays but don’t have all array methods natively.

4. Deduplicate Arrays with Set

const tags = ["js", "css", "js", "html", "css"];
const uniqueTags = [...new Set(tags)];
console.log(uniqueTags); // ["js", "css", "html"]

This is genuinely the cleanest, most readable way to remove duplicates from an array of primitives — no manual loop or .filter(indexOf) trick required.

5. Use structuredClone() for True Deep Copies

const original = { user: { name: "Alice", tags: ["admin", "editor"] } };
const copy = structuredClone(original);

copy.user.tags.push("viewer");
console.log(original.user.tags); // ["admin", "editor"] — untouched

For years, the workaround was JSON.parse(JSON.stringify(obj)), which silently breaks on Date objects, Map, Set, and functions. structuredClone() is a built-in, reliable, genuinely deep clone available natively in modern browsers and Node.js.

6. Group Array Data with reduce() (or Object.groupBy in modern engines)

const orders = [
  { customer: "Alice", total: 20 },
  { customer: "Bob", total: 15 },
  { customer: "Alice", total: 30 },
];

const byCustomer = orders.reduce((acc, order) => {
  (acc[order.customer] ??= []).push(order);
  return acc;
}, {});

console.log(byCustomer);
// { Alice: [{...}, {...}], Bob: [{...}] }

Notice the ??= (logical nullish assignment) inside the reducer — it initializes the array only if it doesn’t already exist, in a single compact expression, instead of a verbose if (!acc[key]) acc[key] = [].

7. Use Tagged Templates for Safe String Building

function safeHTML(strings, ...values) {
  const escape = (str) =>
    String(str).replace(/[&<>"']/g, (c) => ({
      "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;",
    }[c]));

  return strings.reduce((result, str, i) => result + str + (values[i] !== undefined ? escape(values[i]) : ""), "");
}

const userInput = "<script>alert('xss')</script>";
const output = safeHTML`<p>Comment: ${userInput}</p>`;
console.log(output);
// <p>Comment: &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;</p>

Tagged templates give me full control over how interpolated values are processed before they’re inserted into a final string — this exact pattern is how libraries prevent injection attacks while still allowing convenient template syntax.

8. Debounce Expensive Operations

function debounce(fn, delay) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

const handleResize = debounce(() => {
  console.log("Window resized to", window.innerWidth);
}, 250);

window.addEventListener("resize", handleResize);

Debouncing ensures a function only runs after a burst of calls has paused for a specified delay — essential for expensive operations triggered by rapid-fire events like resize, scroll, or keystroke input in a search box.

9. Use Array Destructuring to Swap Variables Without a Temp Variable

let a = 1;
let b = 2;

[a, b] = [b, a];
console.log(a, b); // 2 1

This replaced the classic const temp = a; a = b; b = temp; pattern for me entirely — it’s a small thing, but it comes up constantly, especially in sorting algorithms and state toggles.

10. Use Promise.allSettled() for Resilient Parallel Requests

async function loadDashboardData() {
  const results = await Promise.allSettled([
    fetch("/api/user").then((r) => r.json()),
    fetch("/api/notifications").then((r) => r.json()),
    fetch("/api/stats").then((r) => r.json()),
  ]);

  const [user, notifications, stats] = results.map((r) =>
    r.status === "fulfilled" ? r.value : null
  );

  return { user, notifications, stats };
}

Unlike Promise.all(), which rejects the entire batch if even one promise fails, Promise.allSettled() always resolves with the status of every promise individually — perfect for dashboards or pages made of independent widgets where one failing API call shouldn’t take down the whole page.

Bonus: A Quick Reference Table

TipBest For
?. + ??Safely accessing nested data with sensible fallbacks
Destructuring with defaults/renamingClean, self-documenting function parameters
Spread on array-likesConverting NodeList/arguments to real arrays
new Set()Removing duplicates from arrays of primitives
structuredClone()True deep copies without a library
reduce() + ??=Grouping/aggregating data compactly
Tagged templatesSafe, controlled string interpolation
DebounceLimiting expensive handlers on rapid-fire events
Array destructuring swapSwapping variables without a temp variable
Promise.allSettled()Resilient parallel requests where partial failure is OK

Internal Working Notes

A few of these tricks lean on mechanics worth understanding more deeply:

  • Spread/Array.from() rely on the iterator protocol (Symbol.iterator) for genuine iterables, which is why they work seamlessly on NodeList, Map, Set, and strings, not just arrays.
  • structuredClone() uses the same internal “structured clone algorithm” browsers use for postMessage() and IndexedDB, which is why it correctly handles complex types like Date, Map, and circular references that JSON.stringify() cannot.
  • Debouncing relies on clearTimeout() canceling a pending macrotask before it fires — every new call effectively resets the countdown, which is why only the last call in a rapid burst actually executes.
  • Promise.allSettled() internally wraps each promise so that neither fulfillment nor rejection ever causes early termination of the batch — every promise’s outcome is captured, never thrown, by the time the returned promise resolves.

Practical, Real-World Combination Example

Here’s a small snippet combining several of these tricks together, the way I might actually write it in a real project:

async function loadUniqueRecentTags(userIds) {
  const results = await Promise.allSettled(
    userIds.map((id) => fetch(`/api/users/${id}/tags`).then((r) => r.json()))
  );

  const allTags = results
    .filter((r) => r.status === "fulfilled")
    .flatMap((r) => r.value ?? []);

  return [...new Set(allTags)];
}

This fetches tags for multiple users in parallel, tolerates individual failures gracefully, flattens the results, and deduplicates them — all in a handful of expressive lines.

Best Practices Recap

  • Reach for ?./?? before writing manual if checks for nested or possibly-missing data.
  • Default to structuredClone() over the old JSON-based cloning trick.
  • Use Promise.allSettled() whenever partial failure across parallel requests is acceptable.
  • Debounce (or throttle) any handler attached to high-frequency events like scroll, resize, or input.

Common Mistakes to Avoid

  1. Overusing ?? where || was actually intended, or vice versa — know the difference (nullish vs. any falsy value).
  2. Using Promise.all() when partial results should still be usable.
  3. Forgetting structuredClone() can’t clone functions or DOM nodes — it throws a DataCloneError for those.
  4. Debouncing when throttling was actually the right tool — debounce waits for a pause; throttle guarantees execution at a steady interval regardless of pauses.

FAQs

Q: Is structuredClone() available in Node.js? A: Yes, it’s available natively starting in Node.js 17+, and in all modern browsers.

Q: What’s the difference between debounce and throttle? A: Debounce waits until activity stops for a set delay before running; throttle guarantees the function runs at most once per fixed time interval, regardless of how many times it’s triggered.

Q: Does Promise.allSettled() ever reject? A: No — it always resolves, once every input promise has settled, regardless of whether individual promises fulfilled or rejected.

Q: Can I use ??= with object properties, not just variables? A: Yes: obj.prop ??= defaultValue; works exactly the same way as with a plain variable.

Summary and Key Takeaways

  • Small, well-understood language features — optional chaining, nullish coalescing, destructuring, spread, Set, structuredClone() — compound into dramatically cleaner, more robust code.
  • Understanding why each trick works (iterator protocols, structured clone algorithm, microtask/macrotask timing for debounce) makes them far easier to apply correctly and debug when something goes wrong.
  • Promise.allSettled() and debouncing are especially valuable for building resilient, performant real-world UIs.
  • These techniques aren’t about being clever for its own sake — every one of them exists to solve a genuine, recurring problem I’ve run into repeatedly in production code.

I still occasionally discover a new pattern that makes me rethink how I write everyday JavaScript, and that’s part of what keeps this language interesting even after years of daily use. I hope a few of these save you the time it took me to find them.

References

Total
0
Shares

Leave a Reply

Previous Post
Brute-Forcing HTML From Authentication Using Socket Module

Brute-Forcing HTML From Authentication Using Socket Module

Next Post
Understanding JavaScript's Event Loop

Understanding JavaScript’s Event Loop

Related Posts