Skip to content

JavaScript Style

Standards for consistent JavaScript style.

Follow Code Style Rules

All JavaScript must follow language-agnostic standards: 2-space indentation, 80-character line limit.

Use Double Quotes for Strings

javascript
const message = "Hello world";
const path = "./src/components";

// Use backticks for interpolation or to avoid escaping
const greeting = `Hello ${name}`;
const quotedText = `He said "Hello" to me`;
const links = document.querySelectorAll(`link[rel="stylesheet"]`);

Check for single quotes:

sh
node checks/single-quotes.js file.js

Always Use Semicolons

DON'T rely on automatic semicolon insertion

javascript
const x = y
[a, b] = [1, 2]  // Bug: interpreted as y[a, b]

DO use explicit semicolons

javascript
const x = y;
[a, b] = [1, 2];

Check for missing semicolons:

sh
node checks/missing-semicolons.js file.js

Function Style

Use function declarations, not arrow-assigned variables

DON'T assign arrow functions to variables

javascript
const add = (a, b) => a + b;
const processData = (items) => items.map(transform);

DO use function declarations

javascript
function add(a, b) {
  return a + b;
}

function processData(items) {
  return items.map(transform);
}

Arrow functions for inline callbacks

Arrow functions are fine as arguments to other functions.

javascript
items.forEach((item) => console.log(item));
const names = users.map((u) => u.name);
const active = users.filter((u) => u.isActive);

Methods in objects

Use method shorthand for functions assigned to object keys.

DON'T use arrow functions as object properties

javascript
const api = {
  fetchData: async () => {
    return await fetch("/api/data");
  },
};

DO use method shorthand

javascript
const api = {
  async fetchData() {
    return await fetch("/api/data");
  },
};

Helper functions after first usage

Place helper function definitions after their first usage. This keeps the main logic—the “what”—at the top where readers see it first. The helpers—the “how”—follow below. Rely on hoisting for function declarations.

DON'T define helpers before they're used

javascript
function slugify(text) {
  return text.toLowerCase().replace(/\s+/g, "-");
}

export function createPost(title, body) {
  return {
    slug: slugify(title),
    title,
    body,
    createdAt: Date.now(),
  };
}

DO put main logic first, helpers below

javascript
export function createPost(title, body) {
  return {
    slug: slugify(title),
    title,
    body,
    createdAt: Date.now(),
  };
}

function slugify(text) {
  return text.toLowerCase().replace(/\s+/g, "-");
}

Variable Declarations

Prefer const over let — when a binding is const, you know its value without reading the rest of the scope. let forces you to scan for reassignments. Use let only when there's no other way. Never use var.

DON'T use var

javascript
var count = 0;
var name = "Alice";

DON'T use let when the variable is never reassigned

javascript
let config = { port: 3000 };
let items = [1, 2, 3];

DO use const for bindings that are never reassigned

javascript
const config = { port: 3000 };
const items = [1, 2, 3];
let counter = 0;  // reassigned later

Check for var usage:

sh
node checks/var-usage.js file.js

Loops

Use while, for...of, for...in, or do...while. Never use C-style for (;;) loops.

Check for C-style for loops:

sh
node checks/c-style-for.js file.js

Never for (;;)

Hard to read with everything jammed together, almost never needed, and too low level. Use a more specific construct.

DON'T

javascript
for (let i = 0; i < items.length; i++) {
  process(items[i]);
}

DO

javascript
for (const item of items) {
  process(item);
}

Loop over values

Use for...of to iterate over values in an array, string, Map, Set, or generator.

javascript
for (const user of users) {
  await sendEmail(user);
}

For async iterables, add await before of:

javascript
for await (const chunk of stream) {
  buffer.push(chunk);
}

Loop over keys

Use for...in to iterate over keys.

javascript
for (const key in obj) {
  if (key.startsWith("_")) delete obj[key];
}

Loop over keys and values

Use Object.entries() when you need both.

javascript
for (const [key, value] of Object.entries(config)) {
  console.log(key, value);
}

Works for arrays too, giving you index and value:

javascript
for (const [i, item] of Object.entries(items)) {
  console.log(i, item);
}

Loop until a condition

Use while or do...while when iteration depends on a condition rather than a collection.

When there's a testable stopping condition:

javascript
while (queue.length > 0) {
  const task = queue.shift();
  await task.run();
}

When searching until you find a match:

javascript
while (true) {
  const port = nextPort();
  if (await isAvailable(port)) return port;
  if (port > maxPort) break;
}
throw new Error("No available port");

When the body must run at least once:

javascript
async function retry(fn) {
  const errors = [];
  
  do {
    try {
      return await fn();
    } catch (error) {
      errors.push(error);
    }
  } while (
    errors.length < 3 &&
    await backoff()
  );
  
  throw new AggregateError(errors);
  
  async function backoff() {
    const multiplier = 2 ** (errors.length - 1);
    await delay(1000 * multiplier);
    return true;
  }
}

When assignment fits naturally in the condition:

javascript
let line;
while ((line = reader.readLine())) {
  process(line);
}

Skip an iteration

Use continue for early exit from a single iteration, like an early return in a function.

javascript
for (const file of files) {
  if (file.startsWith(".")) continue;
  await process(file);
}

Array Methods

Use map, filter, find, some, every when they express intent clearly.

javascript
const names = users.map(u => u.name);
const active = users.filter(u => u.isActive);
const admin = users.find(u => u.role === "admin");
const hasAdmin = users.some(u => u.role === "admin");
const allVerified = users.every(u => u.verified);

Avoid reduce—usually less readable than alternatives.

javascript
// ❌ reduce
const byId = users.reduce(
  (acc, u) => ({ ...acc, [u.id]: u }),
  {},
);

// ✅ fromEntries
const byId = Object.fromEntries(
  users.map(u => [u.id, u]),
);

Ternary Operators

When a ternary wraps to multiple lines, keep ? and : at the end of the preceding line, not at the start of the next line.

DON'T put operators before the following expression

javascript
const markup = typeof strings === "string"
  ? strings
  : String.raw({ raw: strings }, ...values);

DO put operators after the preceding expression

javascript
const markup = typeof strings === "string" ?
  strings :
  String.raw({ raw: strings }, ...values);

Trailing Commas

Use trailing commas in multiline structures (objects, arrays, parameters):

javascript
const config = {
  name: "example",
  version: "1.0.0",
  enabled: true,
};

Check for missing trailing commas:

sh
node checks/trailing-commas.js file.js