JavaScript Best Practices

Following best practices in JavaScript helps you write cleaner, more maintainable, and more efficient code. This guide covers essential practices that every JavaScript developer should follow.

Use Strict Mode

Always use strict mode by adding 'use strict'; at the beginning of your JavaScript files or functions. Strict mode helps catch common coding mistakes and "unsafe" actions.

'use strict';

// This will throw an error in strict mode
// because x is not declared
x = 10; 

// This will also throw an error
// because delete cannot be used on a variable
let y = 20;
delete y;

Tip: When using ES6 modules or classes, strict mode is automatically enabled.

Variable Declarations

Use const and let, Avoid var

Use const for values that won't change, and let for values that will. Avoid using var due to its function-scoping and hoisting behavior.

Bad Practice Good Practice
var name = 'John';
var age = 30;
var isActive = true;
const name = 'John';
let age = 30;
const isActive = true;

Declare Variables at the Top

Declare variables at the top of their scope to make your code more readable and to avoid hoisting-related issues.

Good Practice:

function calculateArea(width, height) {
  const area = width * height;
  const perimeter = 2 * (width + height);
  
  console.log(`Area: ${area}`);
  console.log(`Perimeter: ${perimeter}`);
  
  return { area, perimeter };
}

Function Best Practices

Function Declarations vs. Expressions

Prefer function declarations for top-level functions and arrow functions for callbacks and methods.

// Function declaration - hoisted and can be called before defined
function calculateArea(width, height) {
  return width * height;
}

// Arrow function - concise and lexically binds 'this'
const calculatePerimeter = (width, height) => 2 * (width + height);

// Method in an object
const rectangle = {
  width: 10,
  height: 5,
  getArea() {
    return this.width * this.height;
  }
};

Keep Functions Small and Focused

Each function should do one thing and do it well. This makes your code easier to test, debug, and maintain.

Bad Practice Good Practice
function processUserData(user) {
  // Validate user
  if (!user.name) {
    throw new Error('Name is required');
  }
  if (!user.email) {
    throw new Error('Email is required');
  }
  
  // Format user data
  user.name = user.name.trim();
  user.email = user.email.toLowerCase();
  
  // Save user to database
  database.save(user);
  
  // Send welcome email
  emailService.send({
    to: user.email,
    subject: 'Welcome!',
    body: `Hello ${user.name}!`
  });
}
function validateUser(user) {
  if (!user.name) {
    throw new Error('Name is required');
  }
  if (!user.email) {
    throw new Error('Email is required');
  }
  return user;
}

function formatUserData(user) {
  return {
    ...user,
    name: user.name.trim(),
    email: user.email.toLowerCase()
  };
}

function saveUser(user) {
  return database.save(user);
}

function sendWelcomeEmail(user) {
  return emailService.send({
    to: user.email,
    subject: 'Welcome!',
    body: `Hello ${user.name}!`
  });
}

function processUserData(user) {
  const validUser = validateUser(user);
  const formattedUser = formatUserData(validUser);
  const savedUser = saveUser(formattedUser);
  return sendWelcomeEmail(savedUser);
}

Object and Array Best Practices

Use Object and Array Destructuring

Destructuring makes your code cleaner and more readable when working with objects and arrays.

// Object destructuring
const user = { name: 'John', age: 30, email: 'john@example.com' };
const { name, age, email } = user;

// With default values
const { name, age, role = 'User' } = user;

// Array destructuring
const coordinates = [10, 20, 30];
const [x, y, z] = coordinates;

// Skipping elements
const [first, , third] = coordinates;

// Function parameters
function displayUser({ name, age }) {
  console.log(`${name} is ${age} years old`);
}

Use Spread and Rest Operators

Spread and rest operators make working with arrays and objects more concise.

// Spread operator with arrays
const numbers = [1, 2, 3];
const moreNumbers = [...numbers, 4, 5]; // [1, 2, 3, 4, 5]

// Spread operator with objects
const user = { name: 'John', age: 30 };
const userWithEmail = { ...user, email: 'john@example.com' };

// Rest operator in function parameters
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

// Rest operator in destructuring
const [first, ...rest] = [1, 2, 3, 4, 5];

Error Handling

Always handle errors properly to prevent your application from crashing and to provide better user experience.

// Using try-catch
try {
  const data = JSON.parse(userInput);
  processData(data);
} catch (error) {
  console.error('Error processing data:', error.message);
  // Show user-friendly error message
  displayError('Sorry, there was a problem processing your request.');
}

// With async/await
async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Error fetching user data:', error);
    // Handle error appropriately
    return null;
  }
}

Warning: Never use empty catch blocks. Always handle or at least log the error.

Avoid Global Variables

Global variables can lead to naming conflicts and make your code harder to maintain. Use modules, closures, or objects to encapsulate your code.

Bad Practice Good Practice
// Global variables
var userName = 'John';
var userAge = 30;

function displayUser() {
  console.log(`${userName} is ${userAge} years old`);
}
// Using a module pattern
const userModule = (function() {
  const userName = 'John';
  const userAge = 30;
  
  function displayUser() {
    console.log(`${userName} is ${userAge} years old`);
  }
  
  return {
    displayUser
  };
})();

// Or using ES6 modules
// user.js
const userName = 'John';
const userAge = 30;

export function displayUser() {
  console.log(`${userName} is ${userAge} years old`);
}

Use Modern JavaScript Features

Take advantage of modern JavaScript features to write cleaner, more expressive code.

// Template literals
const greeting = `Hello, ${userName}!`;

// Optional chaining
const city = user?.address?.city;

// Nullish coalescing
const role = user.role ?? 'User';

// Array methods
const activeUsers = users.filter(user => user.isActive);
const userNames = users.map(user => user.name);
const totalAge = users.reduce((sum, user) => sum + user.age, 0);

// Object methods
const hasAdmin = users.some(user => user.role === 'Admin');
const allAdults = users.every(user => user.age >= 18);

Performance Best Practices

Avoid Excessive DOM Manipulation

DOM operations are expensive. Minimize them by batching changes and using document fragments.

Bad Practice Good Practice
// Adding items one by one
for (let i = 0; i < 100; i++) {
  const item = document.createElement('li');
  item.textContent = `Item ${i}`;
  list.appendChild(item);
}
// Using a document fragment
const fragment = document.createDocumentFragment();

for (let i = 0; i < 100; i++) {
  const item = document.createElement('li');
  item.textContent = `Item ${i}`;
  fragment.appendChild(item);
}

list.appendChild(fragment);

Debounce and Throttle Event Handlers

Use debouncing and throttling for events that fire frequently, like scrolling, resizing, or typing.

// Debounce function
function debounce(func, delay) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), delay);
  };
}

// Throttle function
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// Usage
const debouncedSearch = debounce(searchFunction, 300);
const throttledScroll = throttle(scrollHandler, 100);

searchInput.addEventListener('input', debouncedSearch);
window.addEventListener('scroll', throttledScroll);

Code Organization

Use Consistent Naming Conventions

Adopt a consistent naming convention for variables, functions, classes, and files.

// Variables and functions: camelCase
const firstName = 'John';
function calculateTotal() { }

// Classes: PascalCase
class UserProfile { }

// Constants: UPPER_CASE or camelCase
const MAX_USERS = 100;
const apiBaseUrl = 'https://api.example.com';

// Private properties/methods: _prefixed or #private (ES2020+)
class User {
  _privateField = 'private';  // Convention
  #truePrivate = 'truly private';  // Language feature
  
  _privateMethod() { }
  #truePrivateMethod() { }
}

Comment Your Code Appropriately

Write meaningful comments that explain why, not what. Use JSDoc for documenting functions and classes.

/**
 * Calculates the total price including tax
 * @param {number} price - The base price
 * @param {number} taxRate - The tax rate as a decimal (e.g., 0.1 for 10%)
 * @returns {number} The total price including tax
 */
function calculateTotalPrice(price, taxRate) {
  // Handle edge cases
  if (price < 0 || taxRate < 0) {
    throw new Error('Price and tax rate must be non-negative');
  }
  
  return price * (1 + taxRate);
}

Testing

Write tests for your code to ensure it works as expected and to catch regressions.

// Example using Jest
describe('calculateTotalPrice', () => {
  test('calculates price with tax correctly', () => {
    expect(calculateTotalPrice(100, 0.1)).toBe(110);
  });
  
  test('handles zero price', () => {
    expect(calculateTotalPrice(0, 0.1)).toBe(0);
  });
  
  test('throws error for negative price', () => {
    expect(() => calculateTotalPrice(-10, 0.1)).toThrow();
  });
});

Security Best Practices

Validate User Input

Always validate and sanitize user input to prevent security vulnerabilities like XSS and injection attacks.

// Validate input before using it
function displayUserComment(comment) {
  if (typeof comment !== 'string') {
    throw new Error('Comment must be a string');
  }
  
  // Sanitize the input to prevent XSS
  const sanitizedComment = DOMPurify.sanitize(comment);
  
  // Now it's safe to insert into the DOM
  commentElement.innerHTML = sanitizedComment;
}

Avoid eval() and new Function()

These functions can execute arbitrary code and pose serious security risks.

Bad Practice:

// Never do this
function calculateFromUserInput(input) {
  return eval(input);  // Dangerous!
}

Next Steps

Now that you understand JavaScript best practices, consider exploring these related topics:

  • Code linting with ESLint
  • Code formatting with Prettier
  • Design patterns in JavaScript
  • Functional programming concepts
  • Advanced testing techniques
  • Performance optimization