20 / 20 · Day 7
Day 7 · Concept 20

Idiomatic modern JS

The ten patterns that separate "I know the language" from "I write modern JS". Immutable-by-default, async/await over .then, destructuring everywhere, types via TS, no var.


1 · const by default; never var

const first; let only when you reassign. var is a relic — function-scoped, hoisted, no good reason to use.

2 · Destructure in signatures

js signatures.js
// Old
function createUser(name, age, role, country) { /* ... */ }

// Modern
function createUser({ name, age, role = "user", country = "US" }) { /* ... */ }

3 · Array methods over loops

js methods.js
// Less idiomatic
const out = [];
for (const x of items) {
    if (x.active) out.push(x.name);
}

// More idiomatic
const out2 = items.filter(x => x.active).map(x => x.name);

4 · async/await over .then

js async.js
// Less idiomatic
function load() {
    return fetch("/api/x")
        .then(r => r.json())
        .then(data => {
            return process(data);
        })
        .catch(err => console.error(err));
}

// More idiomatic
async function load() {
    try {
        const r = await fetch("/api/x");
        const data = await r.json();
        return process(data);
    } catch (err) {
        console.error(err);
    }
}

5 · ?. and ??

user?.profile?.name ?? "Anonymous" replaces five lines of guards.

6 · Spread for immutability

js immut.js
// Don't mutate
const u2 = { ...u, role: "admin" };
const arr2 = [...arr, newItem];

// And for setting nested fields immutably
const updated = {
    ...state,
    user: { ...state.user, name: "Alice" }
};

7 · Use Map for non-string keys

Plain objects coerce keys to strings and have prototype pollution. Map is the right tool when keys aren't compile-time-known strings.

8 · Throw Errors, not strings

js errors.js
// Bad
throw "bad input";

// Good — stack trace and the type system works
throw new Error("bad input");
throw new TypeError("expected number");
class ValidationError extends Error {}
throw new ValidationError("missing field");

9 · TypeScript at the boundary

For new projects in 2026, default to TypeScript. The compile-time guarantees pay for themselves in a week. Use JSDoc + checkJs for legacy JS that you don't want to migrate yet.

10 · Prefer pure functions

Functions that take inputs and return outputs (no mutation, no I/O) are testable and composable. Push side effects (writes, I/O, randomness) to the edges. The middle of your app stays pure.

When it really clicks

  • You write modern idioms automatically and recognise old code as old.
  • You design APIs that exploit destructuring, defaults, and discriminated unions.
  • You instinctively reach for TypeScript at every API boundary.
Found this useful?