Checklist of 11 JavaScript clean code practices from meaningful naming to array spreads

Write maintainable JavaScript with 11 clean code practices

Writing clean, readable, and reusable code is an essential skill for every software engineer. This practice not only makes your code easier to understand and maintain, but it also reduces the time required to onboard new developers. When you write clean code, you're not just solving today's problem. You're making it possible for someone else to understand what you did, to change it safely, and to extend it when requirements shift. These principles become especially clear when you contribute to open source as a frontend developer, where your code lives alongside work from engineers worldwide.

Meaningful Naming#

The first step towards clean code is giving meaningful names to your variables and functions. The names should clearly communicate the purpose, making your code self-explanatory.

naming-bad.js · js
// Bad
let x = 10;
let y = new Date().getFullYear();
if (x > 30) { /*...*/ }
if (y - x > 1990) { /*...*/ }

In the first example, the variables x and y are cryptic and don't reveal their purpose. The second example uses clear and descriptive variable names, improving readability immediately.

naming-good.js · js
// Good
let userAge = 30;
let currentYear = new Date().getFullYear();
if (userAge > 30) { /*...*/ }
if (currentYear - userAge > 1990) { /*...*/ }

Positive Conditionals#

Avoid negative conditionals where possible. They are often harder to understand than their positive counterparts. When you read positive conditionals aloud, they sound like English, making your code more intuitive.

conditional-bad.js · js
// Bad
if (!userExists(user)) { /*...*/ }
conditional-good.js · js
// Good
if (userExists(user)) { /*...*/ }

Single Responsibility Functions#

The Single Responsibility Principle states that a function should do one thing, and do it well. Functions should be concise and shouldn't exceed an average of 30 lines, excluding comments and white space. If a function is doing more than one thing, break it into smaller, more manageable functions.

srp-bad.js · js
// Bad
function createAndDisplayUser(name, age, address) { /*...*/ }
srp-good.js · js
// Good
function createUser(name, age, address) { /*...*/ }
function displayUser(user) { /*...*/ }

Use Default Arguments#

Default arguments make your code cleaner and easier to understand than using short-circuiting or conditionals. They provide default values for undefined arguments. Other falsy values like empty strings, false, null, 0, and NaN will not be replaced by a default value.

defaults-bad.js · js
// Bad
function getUserData(name) {
  const userName = name || "John Doe";
  /*...*/
}
defaults-good.js · js
// Good
function getUserData(name = "John Doe") { /*...*/ }

Maintain a Single Level of Abstraction#

A function should operate at a single level of abstraction. If your function is doing more than that, it's usually an indication that it's doing more than one thing. Dividing a larger function into smaller ones can lead to better reusability and easier testing.

abstraction-bad.js · js
// Bad
function checkSomething(statement) { /*...*/ }
abstraction-good.js · js
// Good
function checkSomething(statement) {
  const tokens = tokenize(statement);
  const syntaxTree = parse(tokens);
  syntaxTree.forEach(node => { /*...*/ });
}
function tokenize(code) { /*...*/ }
function parse(tokens) { /*...*/ }

Don't Ignore Caught Errors#

Caught errors should not be ignored. If an error is captured in a try-catch block, that's an indication that you're expecting a potential error in that code section. You should have a plan for what to do when that error occurs. Merely logging the error to the console isn't sufficient as it can easily get lost among other console messages.

error-bad.js · js
// Bad
try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}
error-good.js · js
// Good
try {
  functionThatMightThrow();
} catch (error) {
  notifyUserOfError(error);
  reportErrorToService(error);
}

Minimize Comments#

While comments can be useful, they should be used sparingly and only when necessary. Good code is self-documenting. If you feel the need to add a comment, consider refactoring your code to make it more clear. The only exception to this rule is when dealing with complex business logic that can't be simplified further.

comments-bad.js · js
// Bad
function hashing(data) {
  // The hash
  let hash = 0;
  // Length of string
  const length = data.length;
  // Loop through every character in data
  for (let i = 0; i < length; i++) {
    // Get character code.
    const char = data.charCodeAt(i);
    // Make the hash
    hash = (hash << 5) - hash + char;
    // Convert to 32-bit integer
    hash &= hash;
  }
}
comments-good.js · js
// Good
function hashing(data) {
  let hash = 0;
  const length = data.length;
  for (let i = 0; i < length; i++) {
    const char = data.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash &= hash; // Convert to 32-bit integer
  }
}

Import Only What You Need#

With ES6, JavaScript introduced destructuring, allowing you to unpack values from arrays or properties from objects into distinct variables. You can use this feature to import only the functions you need from other modules, making your code cleaner and more efficient.

import-bad.js · js
// Bad
import calculate from './calculations';
calculate.add(4,2);
calculate.subtract(4,2);
import-good.js · js
// Good
import { add, subtract } from './calculations';
add(4,2);
subtract(4,2);

Limit Function Arguments#

Limit the number of arguments in a function to make testing easier. Ideally, a function should have one to three arguments. More than that could be a sign that your function is doing too much, which violates the Single Responsibility Principle.

args-bad.js · js
// Bad
function createEmployee(name, age, address, position, salary) { /*...*/ }
args-good.js · js
// Good
function createEmployee(employeeDetails) { /*...*/ }

Use Array Spreads to Copy Arrays#

Copying arrays using array spreads is cleaner and more straightforward than using loops. It reads immediately as an array copy, whereas a loop takes longer to read. ES6 spreads are part of what makes writing elegant JavaScript one-liners possible, combining clarity with brevity.

spread-bad.js · js
// Bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
  itemsCopy[i] = items[i];
}
spread-good.js · js
// Good
const itemsCopy = [...items];

Clean Code as a Discipline#

These 11 practices aren't merely abstract principles. When embedded into your team's workflow, they become the difference between code that's easy to maintain and code that's a burden to change.

Meaningful naming saves your future self hours of detective work. Single responsibility functions make testing straightforward. Explicit error handling prevents silent failures that emerge months later. Proper import statements keep your dependencies clear.

Every code review is a chance to reinforce these practices. When your team values clean code, knowledge spreads. Your developers learn from each other. Code quality improves not because a rule demands it, but because quality becomes how you work.

Write code as you would craft something lasting. Make it readable. Make it maintainable. Make it something your team can extend without fear. That is what clean code practices deliver.

These practices matter most when the code runs in production and the stakes are real. When you invest in custom software development, clean code becomes the difference between code you can still change in three years and code you cannot touch. And when you need maintenance support for systems that matter to your business, the foundation of clean code determines whether that support costs a few thousand or a few million over time.

Questions this post answers

Why is meaningful naming important in JavaScript?
Meaningful names make your code self-explanatory. Variables and functions with clear names communicate their purpose immediately, reducing cognitive load for you and your team. Code with cryptic names like x and y forces you to deduce what they represent, wasting time during maintenance and onboarding.
How does the Single Responsibility Principle improve code?
When a function does one thing well, it becomes easier to test, reuse, and understand. Functions that exceed 30 lines often handle multiple concerns, making them harder to debug and more prone to breaking when requirements change. Splitting responsibilities into smaller functions creates modular code.
When is error handling in a try-catch block important?
Catching an error without handling it properly can hide problems. Logging to the console gets lost among other messages. Instead, notify users of errors and report them to a monitoring service. Proper error handling prevents silent failures and helps you react to problems effectively.

Keep reading