
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)} />
  ));
}