Use Consistent and Meaningful Naming Conventions

Adhering to a consistent naming convention improves code readability and collaboration. In JavaScript, the most common conventions are camelCase for variables and functions, and PascalCase for class names.

// Good
const userAge = 25;
let isActive = true;

// Bad
const user_age = 25; // snake_case is not standard in JS
let active = true;   // not descriptive

Use descriptive names that convey the purpose of the variable or function. Avoid abbreviations unless widely accepted in the community (e.g., id for identifier).


Prefer const and let Over var

Using let and const instead of var avoids common issues related to variable hoisting and scope pollution.

// Good
const PI = 3.14159;
let counter = 0;
if (condition) {
  let x = 10;
}

// Bad
var count = 0;
if (condition) {
  var x = 10; // hoists to the top of the function
}

const should be used for variables that should not be reassigned, while let is for variables that will change.


Modularize Code with Functions and Modules

Breaking code into modular components enhances reusability and testability. Use functions to encapsulate logic and modules to organize related functionality.

// mathUtils.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// main.js
import { add, subtract } from './mathUtils.js';

console.log(add(5, 3)); // 8

Modular code is easier to maintain and test, especially in large applications.


Use Template Literals for String Manipulation

Template literals make string interpolation and multi-line strings much cleaner and more readable than string concatenation.

// Good
const name = 'Alice';
const greeting = `Hello, ${name}! Welcome back.`;

// Bad
const greetingOld = 'Hello, ' + name + '! Welcome back.';

Template literals also support multi-line strings without escape characters.


Avoid Callback Hell with Promises and Async/Await

Asynchronous code should be structured to avoid deeply nested callbacks. Promises and the async/await syntax provide a cleaner and more manageable way to handle asynchronous operations.

// Good
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

// Bad
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Async/await improves code readability and simplifies error handling.


Keep Functions Small and Focused

A function should do one thing and do it well. Functions with multiple responsibilities are harder to test and maintain.

Function DesignDescription
Single responsibilityPerforms only one task
Side-effect-freeDoes not modify external state
PredictableReturns consistent results given the same input
// Good
function calculateTax(amount, taxRate) {
  return amount * taxRate;
}

// Bad
function calculateTaxAndLog(amount, taxRate) {
  const tax = amount * taxRate;
  console.log(`Tax calculated: ${tax}`);
  return tax;
}

Use Default and Destructuring Parameters

Default parameters and destructuring make function calls more flexible and reduce boilerplate code.

// Default parameters
function greet(name = 'Guest') {
  return `Hello, ${name}`;
}

// Destructuring
function getPerson({ name, age = 30 }) {
  return `${name} is ${age} years old.`;
}

const person = { name: 'Bob' };
console.log(getPerson(person)); // Bob is 30 years old.

These features improve function usability and reduce the need for null checks.


Learn more with useful resources