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) => ({
"&": "&", "<": "<", ">": ">", '"': """, "'": "'",
}[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: <script>alert('xss')</script></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
| Tip | Best For |
|---|---|
?. + ?? | Safely accessing nested data with sensible fallbacks |
| Destructuring with defaults/renaming | Clean, self-documenting function parameters |
| Spread on array-likes | Converting 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 templates | Safe, controlled string interpolation |
| Debounce | Limiting expensive handlers on rapid-fire events |
| Array destructuring swap | Swapping 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 onNodeList,Map,Set, and strings, not just arrays. structuredClone()uses the same internal “structured clone algorithm” browsers use forpostMessage()and IndexedDB, which is why it correctly handles complex types likeDate,Map, and circular references thatJSON.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 manualifchecks 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, orinput.
Common Mistakes to Avoid
- Overusing
??where||was actually intended, or vice versa — know the difference (nullish vs. any falsy value). - Using
Promise.all()when partial results should still be usable. - Forgetting
structuredClone()can’t clone functions or DOM nodes — it throws aDataCloneErrorfor those. - 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.