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