javascript best-practices tutorial

JavaScript Best Practices in 2025

Modern JavaScript patterns and practices for writing clean, maintainable, and efficient code.

by Jane Smith
Bowl of fresh strawberry fruits

JavaScript continues to evolve rapidly. Staying current with best practices helps you write better code and build more maintainable applications.

Modern Variable Declarations

Always prefer const by default, use let when reassignment is needed, and avoid var entirely.

// Preferred
const API_URL = 'https://api.example.com';
const users = [];

// When reassignment is needed
let count = 0;
count += 1;

// Avoid
var oldStyle = 'deprecated';

Destructuring

Destructuring makes code more readable and concise:

// Object destructuring
const user = { name: 'John', age: 30, city: 'New York' };
const { name, age } = user;

// Array destructuring
const colors = ['red', 'green', 'blue'];
const [primary, secondary] = colors;

// Default values
const { role = 'user' } = user;

// Renaming
const { name: userName } = user;

Arrow Functions

Use arrow functions for concise syntax and lexical this:

// Simple transformation
const doubled = numbers.map(n => n * 2);

// Multiple parameters
const add = (a, b) => a + b;

// With function body
const processUser = (user) => {
  const { name, email } = user;
  return { name, email: email.toLowerCase() };
};

Template Literals

Template literals improve string handling:

const name = 'World';
const greeting = `Hello, ${name}!`;

// Multi-line strings
const html = `
  <article>
    <h1>${title}</h1>
    <p>${content}</p>
  </article>
`;

// Tagged templates
const sanitized = html`<p>${userInput}</p>`;

Optional Chaining and Nullish Coalescing

Safely access nested properties and provide defaults:

// Optional chaining
const city = user?.address?.city;
const firstItem = arr?.[0];
const result = obj?.method?.();

// Nullish coalescing
const value = input ?? 'default';

// Combined
const userName = user?.name ?? 'Anonymous';

Async/Await

Prefer async/await over raw promises for cleaner code:

// Clean async function
async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw error;
  }
}

// Parallel requests
const [users, posts] = await Promise.all([
  fetchUsers(),
  fetchPosts()
]);

Modern Array Methods

Leverage functional array methods:

const products = [
  { name: 'Apple', price: 1.5, inStock: true },
  { name: 'Banana', price: 0.75, inStock: true },
  { name: 'Cherry', price: 3.0, inStock: false }
];

// Filter
const available = products.filter(p => p.inStock);

// Map
const names = products.map(p => p.name);

// Find
const apple = products.find(p => p.name === 'Apple');

// Some/Every
const hasExpensive = products.some(p => p.price > 2);
const allInStock = products.every(p => p.inStock);

// Reduce
const total = products.reduce((sum, p) => sum + p.price, 0);

Object Shorthand

Use shorthand syntax for cleaner objects:

const name = 'John';
const age = 30;

// Shorthand properties
const user = { name, age };

// Shorthand methods
const calculator = {
  add(a, b) {
    return a + b;
  },
  subtract(a, b) {
    return a - b;
  }
};

// Computed property names
const key = 'dynamic';
const obj = {
  [key]: 'value',
  [`${key}Method`]() {
    return this[key];
  }
};

Spread Operator

Use spread for immutable operations:

// Array operations
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
const copy = [...arr1];

// Object operations
const original = { a: 1, b: 2 };
const extended = { ...original, c: 3 };
const updated = { ...original, b: 10 };

// Function arguments
const numbers = [1, 2, 3];
const max = Math.max(...numbers);

Modules

Organize code with ES modules:

// utils.js
export const formatDate = (date) => {
  return new Intl.DateTimeFormat('en-US').format(date);
};

export const formatCurrency = (amount) => {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  }).format(amount);
};

// main.js
import { formatDate, formatCurrency } from './utils.js';

Error Handling

Implement proper error handling:

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

function validateUser(user) {
  if (!user.email) {
    throw new ValidationError('Email is required', 'email');
  }

  if (!user.email.includes('@')) {
    throw new ValidationError('Invalid email format', 'email');
  }

  return true;
}

try {
  validateUser({ name: 'John' });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error(`Validation failed for ${error.field}: ${error.message}`);
  } else {
    throw error;
  }
}

Conclusion

Modern JavaScript offers powerful features that make code cleaner and more maintainable. Embrace these patterns, but always prioritize readability and team consistency. The best code is code that others can easily understand and maintain.

Fork me on GitHub