Code Readability Tips: Write Code That Humans Understand
Why Readability Trumps Cleverness
Code is read far more often than it is written. A function you compose in ten minutes may be read hundreds of times over its lifetime — by reviewers, by teammates fixing bugs, and by you six months later when you have forgotten the context.
Clever code that saves five seconds to write but costs five minutes to understand is a net loss. Readable code reduces onboarding time, lowers bug rates, and makes code reviews faster.
Name Things to Reveal Intent
The single most impactful readability improvement is better names. A good name answers three questions: what does this thing do, what does it contain, and when should you use it?
Naming Anti-Patterns
| Anti-Pattern | Example | Problem |
|---|---|---|
| Single-letter names | d | No context — duration? distance? date? |
| Abbreviations | usrCfg | Reader must decode the abbreviation |
| Generic names | data, info, tmp | Tells you nothing about the content |
| Type in name | nameString | Redundant with the type system |
| Negated booleans | isNotValid | Double negatives in conditions |
Better Alternatives
// Unclear
const d = getTime()
// Clear
const elapsedSeconds = getTime()
// Unclear
function process(d) { ... }
// Clear
function formatInvoiceDate(rawTimestamp) { ... }
Naming conventions also matter. Use camelCase for JavaScript variables, snake_case for Python, and PascalCase for classes and components. Consistency within a codebase is more important than any single style choice.
Keep Functions Short and Focused
A function should do one thing. If you need the word "and" to describe what it does, split it.
Signs a Function Does Too Much
- It has more than three levels of nesting.
- It takes more than three parameters.
- You struggle to name it without using "and."
- It modifies more than one piece of state.
The Extract Method Pattern
When a function grows too long, extract logical blocks into their own functions. Name the new function after what the block does.
// Before — long function with mixed concerns
function handleSubmit(formData) {
// validate
if (!formData.email.includes('@')) throw new Error('Invalid email')
if (formData.password.length < 8) throw new Error('Short password')
// save
const user = await db.users.create(formData)
// notify
await email.sendWelcome(user.email)
return user
}
// After — each step is a named function
function handleSubmit(formData) {
validateRegistration(formData)
const user = await createUser(formData)
await sendWelcomeEmail(user)
return user
}
The refactored version reads like a table of contents. Each step is self-documenting.
Reduce Nesting with Early Returns
Deep nesting forces the reader to hold multiple conditions in their head simultaneously. Early returns flatten the structure and put the happy path front and center.
// Nested — reader must track three levels
function getDiscount(user) {
if (user) {
if (user.isPremium) {
if (user.yearsActive > 5) {
return 0.25
}
return 0.15
}
return 0.05
}
return 0
}
// Flat — each condition is handled immediately
function getDiscount(user) {
if (!user) return 0
if (!user.isPremium) return 0.05
if (user.yearsActive > 5) return 0.25
return 0.15
}
Use Comments for "Why," Not "What"
Comments that describe what code does are usually a sign the code is not clear enough. Rewrite the code instead. Comments should explain decisions, trade-offs, and non-obvious constraints.
// Bad — restates the code
// Increment i by 1
i += 1
// Good — explains the reason
// API returns 1-based indices but our array is 0-based
i += 1
When Comments Are Valuable
| Situation | Example |
|---|---|
| Workaround for a vendor bug | // Third-party SDK ignores timeout below 100ms |
| Performance trade-off | // Linear search is faster than Set for arrays under 10 items |
| Business rule reference | // Regulation requires 90-day retention (see COMPLIANCE-42) |
| Non-obvious constant | // 0.85 = estimated ratio of active to registered users |
Group Related Code Together
Organize code so related logic is physically close. A reader should not need to scroll between distant files or functions to understand a single feature.
File Organization Tips
- Put types and interfaces near the code that uses them, not in a giant shared types file.
- Group composable imports, reactive state, computed properties, and methods in the same order across all components.
- Keep helper functions in the same file as the component that owns them, until they are reused elsewhere.
Format Consistently
Consistent formatting eliminates visual noise. When half the codebase uses semicolons and the other half does not, every reviewer wastes mental energy on irrelevance.
Tools That Solve Formatting Debates
| Tool | Language | What It Does |
|---|---|---|
| Prettier | JS/TS/CSS/HTML | Auto-formats on save |
| ESLint | JS/TS | Catches style and logic issues |
| Black | Python | Opinionated formatter |
| gofmt | Go | Built-in standard formatter |
Configure formatters to run on save and in CI. Disallow manual formatting overrides.
Avoid Magic Numbers and Strings
Raw values scattered through code force the reader to guess their meaning. Replace them with named constants.
// Magic number — what is 86400?
setTimeout(refreshToken, 86400)
// Named constant — immediately clear
const SECONDS_PER_DAY = 86400
setTimeout(refreshToken, SECONDS_PER_DAY)
This also makes future changes safer — update the constant in one place instead of hunting for every occurrence.
Key Takeaways
- Name things to reveal intent — avoid abbreviations, single letters, and generic terms.
- Keep functions short, focused, and well-named — extract when you need "and" to describe them.
- Flatten nesting with early returns so the happy path is obvious.
- Comment the "why," not the "what" — rewrite unclear code instead of adding comments.
- Replace magic numbers and strings with named constants.
- Use automated formatters so your team never debates style again.
Try It Yourself
Need to convert variable names between conventions? Use the Case Converter to instantly switch between camelCase, PascalCase, snake_case, and more.