JavaScript Clean Code Cheatsheet – Fix Code Smells and Write Maintainable Code

JavaScript Clean Code Cheatsheet – Fix Code Smells and Write Maintainable Code

Writing clean, readable, and maintainable JavaScript is a skill that separates beginner developers from professionals. Over time, it’s easy for projects to accumulate messy patterns — inconsistent naming, deep nesting, magic numbers, or overly long functions. These code smells not only make debugging harder but also slow down teams and increase technical debt.

This JavaScript Clean Code Cheatsheet is your quick reference to writing better, more professional JavaScript. Each section highlights a bad example (code smell) and a clean alternative, helping you recognize poor coding practices and refactor them into elegant, consistent, and maintainable code.

Whether you’re building a new project or refactoring an old one, following these JavaScript best practices will help you:

  • Improve code readability and consistency.
  • Avoid bugs caused by unclear naming and logic.
  • Reduce duplication and complexity.
  • Make your JavaScript easier to test, extend, and collaborate on.

From fixing bad indentation and duplicate code to addressing long functions, tight coupling, and error handling, this cheatsheet covers it all — concise, practical, and ready for everyday development. Bookmark it as your go-to guide to write cleaner, faster, and smarter JavaScript code.

Code Smell to Clean Code: With JavaScript Examples

Formatting & Readability: Indentation & Spacing

Poor indentation and lack of spacing make code hard to scan and maintain. Always use consistent indentation (2 or 4 spaces) and spacing around operators for readability.

// 🔴 Smell: Bad Indentation and lack of Spacing
const sum=(a,b)=>{
const result=a+b;
return result;
}

// 🟢 Clean: Proper Indentation and Spacing
const sum = (a, b) => {
  const result = a + b;
  return result;
};

Formatting & Readability: Consistent Line Breaks

Avoid long lines and clusters of statements. Break statements logically and use line breaks to visually group related code so it's easier to read and maintain.

// 🔴 Smell: Hard to read
if(isLoggedIn){showDashboard();updateUserActivity();logAnalytics();}

// 🟢 Clean: Structured and readable
if (isLoggedIn) {
  showDashboard();
  updateUserActivity();
  logAnalytics();
}

Formatting & Readability: Inconsistent Syntaxes

Mixing different coding styles (quotes, semicolons, function styles) causes confusion and inconsistency. Pick a style guide and be consistent.

// 🔴 Smell: Mixed styles
function greet(name) {
  return "Hello, " + name
}

const bye = (name) => {
  return 'Goodbye, ' + name;
};

// 🟢 Clean: Consistent modern syntax
const greet = (name) => {
  return `Hello, ${name}`;
};

const bye = (name) => {
  return `Goodbye, ${name}`;
};

Formatting & Readability: Inconsistent Naming Cases

Inconsistent use of camelCase, PascalCase, or snake_case makes code unpredictable and error-prone. Choose a naming convention and apply it consistently across the codebase.

// 🔴 Smell: Inconsistent naming
let user_name = "Tapas";
let UserAge = 32;
let isloggedIn = true;

// 🟢 Clean: Consistent camelCase
let userName = "Tapas";
let userAge = 32;
let isLoggedIn = true;

Formatting & Readability: Overuse of Comments

Too many comments clutter code. Write expressive code and use comments to explain intent, not syntax.

// 🔴 Smell: Over-commented code
// Create user object
// Assign name and email
// Save to database
function saveUser(name, email) {
  const user = { name, email };
  db.save(user);
}

// 🟢 Clean: Code is self-explanatory
function saveUser(name, email) {
  db.save({ name, email });
}

Naming & Structure: Clear Variable Names

Meaningless variable names make it hard to understand intent. Use descriptive names that express purpose and context so other developers (and future you) can read intent quickly.

// 🔴 Smell: Unclear variable names
let x = 10; let y = 20;

// 🟢 Clean: Descriptive variable names
let width = 10; let height = 20;

Naming & Structure: Too Many Parameters

Functions with many parameters are hard to read and easy to misuse. Group related parameters into objects (or use a config object) to improve readability and extensibility.

// 🔴 Smell: Too many parameters
function createUser(name, age, email, address, isAdmin) {}

// 🟢 Clean: Use an object parameter
function createUser({ name, age, email, address, isAdmin }) {}

Naming & Structure: Ambiguous Function Purpose

Functions should have a clear and single responsibility. Avoid mixing unrelated actions that reduce clarity.

// 🔴 Smell: Function doing multiple things
function updateUser(user) {
  user.name = 'Tapas';
  sendEmail(user);
  logChange(user);
}

// 🟢 Clean: Separate concerns
function updateUser(user) {
  user.name = 'Tapas';
}

function notifyUserUpdate(user) {
  sendEmail(user);
  logChange(user);
}

Naming & Structure: Non-standard File Naming

Inconsistent or non-standard file names lead to confusion in large projects. Follow conventions like kebab-case for components and utilities.

// 🔴 Smell: Random naming
HeaderFile.js
userUTILS.JS
Dbhelper.js

// 🟢 Clean: Consistent file naming
header-file.js
user-utils.js
db-helper.js

Code Complexity: Magic Numbers/Strings

Avoid unexplained literals (magic numbers/strings). Use named constants to express meaning and make values easier to change and document.

// 🔴 Smell: Magic values
if (user.role === "01") discount = 0.1;

// 🟢 Clean: Named constants
const ADMIN_ROLE = "01";
const ADMIN_DISCOUNT = 0.1;

if (user.role === ADMIN_ROLE) discount = ADMIN_DISCOUNT;

Code Complexity: Long Function

Functions that do too much are hard to test and reason about. Break long functions into smaller functions with single responsibilities.

// 🔴 Smell: Long function doing many things
function handleOrder() {
  validateInput();
  checkInventory();
  calculateTotal();
  processPayment();
  sendEmail();
  updateAnalytics();
}

// 🟢 Clean: Decompose into focused functions
function handleOrder() {
  validateOrder();
  processPayment();
  notifyUser();
}

function validateOrder() {
  validateInput();
  checkInventory();
}

function notifyUser() {
  sendEmail();
  updateAnalytics();
}

Code Complexity: Larger Classes

Classes with many responsibilities violate the Single Responsibility Principle. Split large classes into smaller services that each handle a clear concern.

// 🔴 Smell: God class with many responsibilities
class UserManager {
  createUser() {}
  deleteUser() {}
  sendEmail() {}
  logUserActivity() {}
}

// 🟢 Clean: Separate responsibilities into classes/services
class UserService {
  createUser() {}
  deleteUser() {}
}

class NotificationService {
  sendEmail() {}
}

class Logger {
  logUserActivity() {}
}

Code Complexity: Complex Loops

Overly nested or complicated loops reduce clarity. Use array methods like map, filter, or reduce for Cleaner intent.

// 🔴 Smell: Complex loop logic
for (let i = 0; i < users.length; i++) {
  if (users[i].isActive && users[i].age > 18) {
    active.push(users[i].name);
  }
}

// 🟢 Clean: Expressive functional style
const active = users
  .filter(u => u.isActive && u.age > 18)
  .map(u => u.name);

Code Complexity: Nested Ternary Hell

Avoid nested ternary operators that harm readability. Replace with clear conditionals or helper functions.

// 🔴 Smell: Nested ternary
const status = user
  ? user.isAdmin
    ? 'Admin'
    : 'User'
  : 'Guest';

// 🟢 Clean: Readable conditional
let status;
if (!user) status = 'Guest';
else if (user.isAdmin) status = 'Admin';
else status = 'User';

Logic & Maintainability: Duplicate Code

Duplicating logic increases maintenance cost and introduces inconsistencies. Abstract repeated logic into a reusable function to follow DRY (Don't Repeat Yourself).

// 🔴 Smell: Duplicate logic
function addUser() { saveToDB(); logAction(); }
function removeUser() { saveToDB(); logAction(); }

// 🟢 Clean: Reuse behavior
function performUserAction(action) {
  saveToDB();
  logAction(action);
}

Logic & Maintainability: Long Conditional / Nested If

Deeply nested conditionals reduce readability. Use guard clauses or early returns to flatten control flow and make the main path clearer.

// 🔴 Smell: Deeply nested conditionals
if (user) {
  if (user.isActive) {
    if (user.role === 'admin') {
      accessAdminPanel();
    }
  }
}

// 🟢 Clean: Guard clause / early return
if (!user || !user.isActive || user.role !== 'admin') return;
accessAdminPanel();

Logic & Maintainability: Deep Nesting / Pyramid DOM

Excessive nesting in UI code (or logic) makes components hard to reason about. Use early returns, smaller components, or conditional helpers to flatten nesting.

// 🔴 Smell: Pyramid of nested JSX
<div>
  {isLoggedIn ? (
    <div>
      {user.isAdmin ? (
        <AdminPanel />
      ) : (
        <UserPanel />
      )}
    </div>
  ) : (
    <LoginForm />
  )}
</div>

// 🟢 Clean: Flattened and readable
{!isLoggedIn && <LoginForm />}
{isLoggedIn && (user.isAdmin ? <AdminPanel /> : <UserPanel />)}

Logic & Maintainability: Temporary Variable Abuse

Unnecessary temporary variables add noise. Return expressions directly when it keeps clarity and avoids extraneous identifiers.

// 🔴 Smell: Unnecessary temporary variable
const total = price * quantity;
return total;

// 🟢 Clean: Return expression directly
return price * quantity;

Logic & Maintainability: Neglecting Default Cases

Switch statements or enums should always handle unexpected cases. Missing defaults can lead to silent errors.

// 🔴 Smell: Missing default case
switch (role) {
  case 'admin': doAdmin(); break;
  case 'user': doUser(); break;
}

// 🟢 Clean: Always handle fallback
switch (role) {
  case 'admin': return doAdmin();
  case 'user': return doUser();
  default: return handleUnknown();
}

Logic & Maintainability: Implicit Type Coercion

Loose equality (==) can produce confusing results. Always use strict equality (===) for predictable behavior.

// 🔴 Smell: Loose equality
if (count == '5') {}

// 🟢 Clean: Strict equality
if (count === 5) {}

Design & Architecture: Primitive Obsession

Relying on primitives to model complex concepts leads to fragile code. Use objects, classes, or value objects to give structure and intent.

// 🔴 Smell: Primitives for structured data
const order = ["Tapas", "123 Main St", 2, 299];

// 🟢 Clean: Use object with named fields
const order = {
  customerName: "Tapas",
  address: "123 Main St",
  quantity: 2,
  totalPrice: 299
};

Design & Architecture: Tight Coupling / Shotgun Surgery

When small changes force edits across many modules, the system is tightly coupled. Encapsulate responsibilities and create clear interfaces to reduce ripple effects.

// 🔴 Smell: Directly mixing responsibilities
function sendEmail(user) {
  console.log("Email sent to", user.email);
  updateUserStatus(user.id, "emailed");
  addLog("Email", user.id);
}

// 🟢 Clean: Delegate responsibilities
function sendEmail(user) {
  console.log("Email sent to", user.email);
  notifyUserActions(user.id);
}

function notifyUserActions(userId) {
  updateUserStatus(userId, "emailed");
  addLog("Email", userId);
}

Design & Architecture: Global State Abuse

Overusing global variables leads to hidden dependencies and unexpected mutations. Use scoped variables or state managers.

// 🔴 Smell: Global mutable state
let userCount = 0;
function addUser() { userCount++; }

// 🟢 Clean: Scoped or managed state
function createUserManager() {
  let count = 0;
  return {
    addUser: () => count++,
    getCount: () => count
  };
}

Design & Architecture: Hidden Side Effects

Functions that change external state without clear intent make debugging hard. Use pure functions that return new values instead.

// 🔴 Smell: Function mutates external variable
let total = 0;
function addToTotal(amount) { total += amount; }

// 🟢 Clean: Pure function
function addToTotal(total, amount) {
  return total + amount;
}

Error Handling & Resilience: Ignoring Errors

Not handling errors can crash applications or leak incorrect states. Use try/catch, validations, and error boundaries to handle failures gracefully and return meaningful feedback.

// 🔴 Smell: Ignoring potential errors
const data = JSON.parse(jsonString);

// 🟢 Clean: Handle errors gracefully
let data;
try {
  data = JSON.parse(jsonString);
} catch (error) {
  console.error("Invalid JSON:", error);
  data = null; // or fallback
}

Error Handling & Resilience: Swallowing Errors

Catching errors but not handling or logging them hides important issues and makes debugging difficult. Always log or handle exceptions meaningfully.

// 🔴 Smell: Silent catch block
try {
  riskyOperation();
} catch (e) {
  // ignored
}

// 🟢 Clean: Log or handle the error
try {
  riskyOperation();
} catch (e) {
  console.error('Operation failed:', e.message);
  showUserFriendlyMessage();
}

Error Handling & Resilience: Assuming Success

Ignoring potential failures from async operations can cause runtime crashes or undefined behavior. Always check for success responses or handle errors.

// 🔴 Smell: Assuming request always succeeds
const data = await fetch('/api/user').then(res => res.json());

// 🟢 Clean: Handle possible errors
try {
  const res = await fetch('/api/user');
  if (!res.ok) throw new Error('Network error');
  const data = await res.json();
} catch (err) {
  console.error('Failed to fetch user:', err);
}

Error Handling & Resilience: Inconsistent Error Handling

Mixing different error-handling styles (callbacks, promises, try/catch) creates confusion. Use a consistent async/await pattern throughout your codebase.

// 🔴 Smell: Mixing callback and promise error handling
getData((err, data) => {
  if (err) throw err;
  processData(data).catch(console.error);
});

// 🟢 Clean: Consistent async/await error handling
try {
  const data = await getDataAsync();
  await processData(data);
} catch (err) {
  console.error('Error processing data:', err);
}

Performance & Efficiency: Unnecessary Loops

Looping multiple times over the same data adds redundancy and increases computational cost. Combine operations when possible for cleaner, faster code.

// 🔴 Smell: Multiple loops over same array
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(n => n % 2 === 0);
const doubled = [];
evens.forEach(e => doubled.push(e * 2));

// 🟢 Clean: Combine using reduce
const doubledEvens = numbers.reduce((acc, n) => {
  if (n % 2 === 0) acc.push(n * 2);
  return acc;
}, []);

Performance & Efficiency: Blocking Operations

Long-running synchronous loops block the main thread and freeze the UI. Defer or split heavy tasks to keep the app responsive.

// 🔴 Smell: CPU-heavy loop blocks UI
for (let i = 0; i < 1e9; i++) {
  heavyCalculation(i);
}

// 🟢 Clean: Offload using setTimeout or Web Workers
function runHeavyTask() {
  setTimeout(() => {
    for (let i = 0; i < 1e9; i++) heavyCalculation(i);
  }, 0);
}
runHeavyTask();

Performance & Efficiency: Unnecessary Re-Renders (React)

Passing new inline functions or non-memoized props causes avoidable re-renders. Memoize callbacks or components for better performance.

// 🔴 Smell: Inline function recreated on every render
function List({ items }) {
  return items.map(item => (
    <Item key={item.id} onClick={() => alert(item.name)} />
  ));
}

// 🟢 Clean: Memoize the callback
function List({ items }) {
  const handleClick = useCallback((name) => alert(name), []);
  return items.map(item => (
    <Item key={item.id} onClick={() => handleClick(item.name)} />
  ));
}

Learn Full Stack

with tapaScript

By clicking Sign Up you re confirming that you agree with our Terms and Conditions.

newsletter

Don't forget to connect with us on