JavaScript Comments — Best Practices, JSDoc, and Clean Code in 2026
Advertisement
Introduction
Why This Matters
Comments are one of the first things new developers learn and one of the last things they master. Bad comments — outdated, redundant, or misleading — are worse than no comments at all because they create false confidence. Good comments reduce onboarding time, catch subtle bugs during code review, and keep complex business logic understandable six months later. In 2026, with AI-assisted coding becoming standard, well-commented code also produces better AI suggestions, because context is king.
The Two Types of JavaScript Comments
JavaScript has two syntaxes for comments, and each has a distinct role:
// Single-line comment — for brief inline notes
/*
Multi-line comment — for longer explanations,
block-level documentation, or temporarily disabling code
*/Single-line comments work well for line-level intent. Multi-line comments (and JSDoc's triple-slash variant /**) are better for functions, modules, and anything that needs structured documentation.
JSDoc — The Professional Standard
JSDoc is the standard for documenting JavaScript (and TypeScript) functions. It turns comments into structured, machine-readable documentation that IDEs and tools like TypeDoc can parse:
/**
* Calculates the total price after applying a percentage discount.
*
* @param {number} price - Original price in USD (must be positive)
* @param {number} discount - Discount percentage (0–100)
* @returns {number} Discounted price rounded to 2 decimal places
* @throws {RangeError} If discount is not between 0 and 100
*
* @example
* applyDiscount(100, 20) // returns 80.00
* applyDiscount(49.99, 5) // returns 47.49
*/
function applyDiscount(price, discount) {
if (discount < 0 || discount > 100) {
throw new RangeError(`Discount must be between 0 and 100, got ${discount}`)
}
return parseFloat((price * (1 - discount / 100)).toFixed(2))
}JSDoc annotations give you type hints in VS Code even in plain JavaScript files — without requiring a full TypeScript migration.
Documenting Complex Modules and Classes
For a class or module, a block comment at the top sets the context for all readers:
/**
* @module AuthService
* @description Handles user authentication, token management, and session
* persistence. Uses JWT for stateless auth and bcrypt for password hashing.
*
* Security note: refresh tokens are stored in httpOnly cookies only.
* Never expose them via JavaScript-accessible storage.
*/
class AuthService {
/**
* @param {import('./db').Database} db - Injected database connection
* @param {string} jwtSecret - Secret for signing JWT tokens
*/
constructor(db, jwtSecret) {
this.db = db
this.jwtSecret = jwtSecret
}
/**
* Authenticates a user and returns a signed JWT.
*
* @param {string} email - User email address
* @param {string} password - Plaintext password (hashed internally)
* @returns {Promise<string>} Signed JWT valid for 1 hour
* @throws {AuthError} If credentials are invalid
*/
async login(email, password) {
const user = await this.db.users.findByEmail(email)
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
throw new AuthError('Invalid credentials')
}
return jwt.sign({ sub: user.id, email }, this.jwtSecret, { expiresIn: '1h' })
}
}Inline Comments: What, Why, and When
The most important rule of inline comments is: explain the why, not the what.
// ❌ Redundant — the code already says this
i++ // increment i by 1
// ✅ Explains intent that isn't obvious from the code
const RETRY_LIMIT = 3 // API rate-limits after 3 rapid retries per second
// ❌ Redundant
const total = price * quantity // multiply price by quantity
// ✅ Explains a non-obvious business rule
const total = price * quantity * 1.08 // 8% sales tax required in CA by AB-147When you feel the urge to write a comment explaining what the code does, consider whether the code can be rewritten to be self-explanatory instead:
// ❌ Needs a comment because the code is unclear
// Check if the user is an admin who hasn't been banned
if (u.r === 1 && u.s !== 2) { ... }
// ✅ Self-documenting — no comment needed
const isActiveAdmin = user.role === 'admin' && user.status !== 'banned'
if (isActiveAdmin) { ... }TODO and FIXME Comments
Structured task comments help teams track technical debt. Use a consistent format so tools like ESLint and IDEs can surface them:
// TODO(sanjeevsharma): Replace with native Temporal API once Node 22 LTS ships
const formatDate = (d) => new Date(d).toLocaleDateString()
// FIXME: Race condition when two requests arrive within the same tick
// See issue #482 — requires mutex or queue-based approach
async function processPayment(orderId) { ... }
// HACK: The upstream API returns dates in M/D/YY format — normalise before parsing
const normalised = rawDate.replace(/(\d+)\/(\d+)\/(\d+)/, '20$3-$1-$2')
// NOTE: This intentionally returns null instead of throwing — callers must check
function findUser(id) { return users.find(u => u.id === id) ?? null }Many teams lint for unresolved FIXME tags to prevent them from reaching production.
Commenting for Debugging vs Commenting for Documentation
These are different use cases that require different approaches:
// Debugging comment — temporary, remove before merging
console.log('[DEBUG] payload:', JSON.stringify(payload, null, 2))
// Documentation comment — permanent, meaningful to future readers
// We clone the array here to prevent the sort from mutating the original
// data prop, which would cause unexpected re-renders in the parent component
const sorted = [...items].sort((a, b) => a.name.localeCompare(b.name))The best teams use lint rules (e.g. no-console) to prevent debug comments from reaching production and code review checklists to ensure documentation comments are accurate.
Commenting in TypeScript Codebases
In TypeScript, type annotations handle much of what comments once had to do. Comments can then focus purely on intent:
/**
* Retries an async operation with exponential back-off.
* Useful for transient network failures and rate-limited APIs.
*
* @param fn - The async operation to retry
* @param retries - Maximum number of attempts (default: 3)
* @param delay - Base delay in ms, doubled each attempt (default: 300)
*/
async function withRetry<T>(
fn: () => Promise<T>,
retries = 3,
delay = 300
): Promise<T> {
try {
return await fn()
} catch (err) {
if (retries === 0) throw err
// Exponential back-off: 300ms, 600ms, 1200ms
await new Promise(res => setTimeout(res, delay))
return withRetry(fn, retries - 1, delay * 2)
}
}Common Mistakes
- Writing redundant comments that just restate what the code already says — these add noise, not signal.
- Leaving stale comments after refactoring — outdated comments actively mislead readers.
- Over-commenting simple utility functions and under-commenting complex business logic — prioritise where complexity lives.
- Using comments to disable broken code instead of deleting it — use version control for history.
- Missing JSDoc on public APIs — any function used across modules deserves JSDoc so consumers get IDE hints without reading the source.
Best Practices
- Write JSDoc for every exported function, class, and module — include
@param,@returns, and@throws. - Comment the why and constraints, not the what — code shows what; comments explain intent and trade-offs.
- Treat
FIXMEandHACKtags as technical debt tickets — link to an issue tracker where possible. - Keep comments co-located with the code they describe; never in a separate document that drifts out of sync.
- Use consistent formatting across the team — agree on single-line vs. JSDoc style in your ESLint/Prettier config.
- Review comments in code review the same way you review code — a misleading comment is a bug.
Key Takeaways
- JavaScript has two comment syntaxes:
//for single-line and/* */for multi-line; JSDoc uses/** */for structured documentation. - JSDoc is the professional standard for documenting functions and generates IDE tooltips, type hints, and API documentation without TypeScript.
- Good comments explain why code exists or the constraints it operates under, not what the code mechanically does.
- Stale, outdated comments are worse than no comments — they create false confidence and mislead future developers.
- Structured tags like
TODO,FIXME, andHACKhelp teams track technical debt and can be surfaced by linters. - In TypeScript codebases, type annotations handle the data-shape documentation, freeing comments to focus on business intent.
- Self-documenting code through meaningful variable and function names often eliminates the need for inline comments.
- Code review should evaluate comments with the same rigour as code — inaccurate documentation is a defect.
Advertisement