Code Style
Code style is the set of small decisions you make every time you write a function, name a variable, or split a file. Done well, the people reading your code (often you in three months) glide through it without thinking. Done badly, every line slows them down. Style is not about being pedantic — it is about reducing the cognitive load on the next reader.
Naming things
JavaScript leans on three conventions. Use them consistently and the kind of thing becomes obvious from its name alone.
camelCase for variables, parameters and functions —
userEmail,getCartTotal,isReady.PascalCase for classes and constructor-like factories —
Order,UserService,EventEmitter.UPPER_SNAKE_CASE for module-level constants that never change —
MAX_RETRIES,API_BASE_URL.Leading underscore is a soft "do not touch" signal —
_internalCache. For real privacy, prefer#privateFieldor a closure.
naming.js
const MAX_RETRIES = 3;
class PaymentClient {
#apiKey;
constructor(apiKey) { this.#apiKey = apiKey; }
async charge(amountInCents, currency) {
return this.#post('/charge', { amountInCents, currency });
}
}
function isExpired(token, now = Date.now()) {
return token.expiresAt < now;
}Descriptive over clever
A name should tell the reader what the value is or what the function does. Single-letter names are fine for short loops and math; everywhere else they cost you.
rename-for-meaning.js
// Hard to read
function f(a, b) {
return a.filter(x => x.s === b).map(x => x.t);
}
// Self-documenting
function titlesByStatus(tasks, status) {
return tasks
.filter(task => task.status === status)
.map(task => task.title);
}Small functions, one job
The most reliable refactor in any codebase is "extract a function". If you cannot describe what a block does in a short verb phrase, that block probably wants to be its own function.
extract-function.js
// Doing too much
function checkout(cart, user) {
let total = 0;
for (const item of cart.items) total += item.price * item.qty;
if (user.isMember) total *= 0.9;
if (total > 100) total -= 5;
return total;
}
// Each step has a name
const sum = items => items.reduce((t, i) => t + i.price * i.qty, 0);
const memberPrice = (total, user) => user.isMember ? total * 0.9 : total;
const bulkRebate = total => total > 100 ? total - 5 : total;
function checkout(cart, user) {
return bulkRebate(memberPrice(sum(cart.items), user));
}A rough guideline: if a function does not fit on one screen, look hard for a piece that can be lifted out with a clean name.
Return early, indent less
Deeply nested if blocks make the eye work hard. Handle the failure or edge cases first and return, leaving the happy path flush against the left margin.
early-return.js
// Pyramid of doom
function publish(post, user) {
if (user) {
if (user.isAdmin) {
if (post.isValid) {
return savePost(post);
}
}
}
return null;
}
// Guard clauses
function publish(post, user) {
if (!user) return null;
if (!user.isAdmin) return null;
if (!post.isValid) return null;
return savePost(post);
}Comments — the why, not the what
Good code says what it does. Good comments say why it does it that way: a tricky tradeoff, a workaround for a bug, a non-obvious business rule. If a comment describes mechanics the code already shows, delete it and improve the names instead.
comments.js
// Bad — restates the obvious
// Loop over the items
for (const item of items) { /* ... */ }
// Good — explains a non-obvious decision
// Stripe rounds half-to-even at the cent level, so we
// match their rule here to avoid a 1-cent drift on
// reconciliation reports.
function roundCents(amount) {
return Math.round(amount * 100) / 100;
}Use // TODO: or // FIXME: to flag things you know are not finished — and add a date or ticket so future-you knows the context.
Const by default, let when you mutate
Reach for const first. Switch to let only when you reassign. var should not appear in new code. A const does not stop you mutating an object — it only stops the binding from being reassigned, which is exactly what we want most of the time.
const-by-default.js
const config = loadConfig();
const retries = 3;
let attempt = 0;
while (attempt < retries) {
try { return await call(); }
catch { attempt += 1; }
}Formatting is automated, not argued
Spaces vs tabs, semicolons, line length — none of that should consume a code review. Put a formatter in your project and let it decide. The win is psychological: you stop noticing formatting and start seeing the code.
Prettier — handles whitespace, line breaks, quote style automatically.
ESLint — flags real problems: unused variables, unreachable code, suspicious comparisons.
Run both on save in your editor and as a pre-commit hook, so bad style never reaches a teammate.