Patterns#

Idioms that show up across well-written JavaScript codebases.

Strict Equality#

Always use === and !==. The loose == performs implicit type coercion that produces surprising results.

"" == 0           // true
"0" == false      // true
null == undefined // true (the only intentionally useful loose case)

Use const by Default#

Reach for const first, let when reassignment is genuinely needed, var essentially never. const doesn’t make objects immutable; it just prevents rebinding the name.

Optional Chaining + Nullish Coalescing#

Reach into possibly missing structures without ladders of guards.

const city = response?.user?.address?.city ?? "unknown";

Async Without Await Loops#

Sequential await in a loop serialises requests. To run in parallel:

// Slow: each fetch waits for the previous
for (const url of urls) await fetch(url);

// Fast: all in flight at once
await Promise.all(urls.map((u) => fetch(u)));

If failures shouldn’t abort the rest, use Promise.allSettled.

Modules over Globals#

Export discrete units. Avoid mutating the global object; avoid implicit shared state. import / export keeps dependencies explicit and helps tree-shaking.

Immutability for Inputs#

Treat function arguments as read-only. Returning a new object beats mutating a shared one.

function withRole(user, role) {
  return { ...user, role };          // new object
}

For arrays, toSorted, toReversed, toSpliced, and with (ES2023) return new arrays without mutating the original.

Discriminated Objects#

Model variants with a tag field.

const event = { type: "click", x: 10, y: 20 };

switch (event.type) {
  case "click":  handleClick(event); break;
  case "scroll": handleScroll(event); break;
}

Module Side Effects#

Avoid running code (HTTP calls, registrations, mutations) at module load time. Export functions; let the caller decide when to invoke them. This makes modules testable and tree-shakeable.

Error Strategy#

Throw Error (or a subclass); never strings or plain objects. At system boundaries, catch, log, and translate. In between, let exceptions propagate.

class NotFoundError extends Error {
  constructor(id) {
    super(`not found: ${id}`);
    this.name = "NotFoundError";
    this.id = id;
  }
}