Skip to content

Modern JS Best Practices

This lesson explains Modern JS Best Practices with clear examples, use cases, and best practices so you can use modern JavaScript confidently in real projects.

ES6 Best Practices

ES6 best practices help developers write modern, clean, readable, maintainable, and scalable JavaScript. These practices are useful for frontend applications, backend Node.js projects, React apps, API integrations, interviews, and real-world enterprise development.

50 ES6 Best Practices with Examples

  • 1. Use const by default.
    Use const when the variable reference should not be reassigned.
    const appName = "Dashboard";
  • 2. Use let only when reassignment is needed.
    Use let for counters, loops, and values that change.
    let count = 0;
    
    count += 1;
  • 3. Avoid var.
    var is function-scoped and can create hoisting confusion. Prefer let and const.
    let total = 100;
    const tax = 8;
  • 4. Use arrow functions for callbacks.
    Arrow functions keep callback code shorter and cleaner.
    const numbers = [1, 2, 3];
    
    const doubled =
      numbers.map((number) => number * 2);
  • 5. Use regular functions for object methods.
    Regular methods have their own this.
    const user =
      {
        name: "Alice",
    
        showName() {
          return this.name;
        }
      };
  • 6. Use template literals for readable strings.
    Template literals make variables inside strings easier to read.
    const name = "John";
    
    const message =
      `Welcome, ${name}!`;
  • 7. Use destructuring for objects.
    Destructuring reduces repeated property access.
    const user =
      {
        name: "Alice",
        role: "Admin"
      };
    
    const { name, role } =
      user;
  • 8. Use destructuring for arrays.
    Array destructuring makes position-based values cleaner.
    const colors =
      ["red", "green", "blue"];
    
    const [firstColor, secondColor] =
      colors;
  • 9. Use default parameters.
    Default parameters avoid manual fallback checks.
    function greet(name = "Guest") {
      return `Hello ${name}`;
    }
  • 10. Use rest parameters instead of arguments.
    Rest parameters are clearer and work well with arrow functions.
    function sum(...numbers) {
      return numbers.reduce(
        (total, number) => total + number,
        0
      );
    }
  • 11. Use spread syntax to copy arrays.
    Spread creates a shallow copy without mutating the original array.
    const items = ["A", "B"];
    
    const copy =
      [...items];
  • 12. Use spread syntax to merge arrays.
    Spread makes array merging readable.
    const frontend =
      ["HTML", "CSS"];
    
    const advanced =
      ["JavaScript", "React"];
    
    const skills =
      [...frontend, ...advanced];
  • 13. Use spread syntax to copy objects.
    Object spread is useful for immutable updates.
    const user =
      {
        name: "Alice",
        active: true
      };
    
    const updatedUser =
      {
        ...user,
        active: false
      };
  • 14. Use enhanced object property shorthand.
    When key and variable names match, use shorthand.
    const name = "Alice";
    const role = "Admin";
    
    const user =
      {
        name,
        role
      };
  • 15. Use concise object methods.
    ES6 method syntax is cleaner than assigning function expressions.
    const calculator =
      {
        add(a, b) {
          return a + b;
        }
      };
  • 16. Use computed property names when needed.
    Computed properties help build dynamic object keys.
    const field = "email";
    
    const formData =
      {
        [field]: "user@example.com"
      };
  • 17. Use map() for transformations.
    Use map() when every item should become a new value.
    const prices =
      [10, 20, 30];
    
    const withTax =
      prices.map((price) => price * 1.08);
  • 18. Use filter() for selecting items.
    Use filter() when only matching items should remain.
    const users =
      [
        { name: "Alice", active: true },
        { name: "Bob", active: false }
      ];
    
    const activeUsers =
      users.filter((user) => user.active);
  • 19. Use reduce() for totals and summaries.
    reduce() is useful when an array becomes one result.
    const total =
      [10, 20, 30].reduce(
        (sum, value) => sum + value,
        0
      );
  • 20. Use find() for one matching item.
    Use find() instead of filter() when you need only one result.
    const product =
      products.find(
        (item) => item.id === 101
      );
  • 21. Use some() for at-least-one checks.
    some() returns true when at least one item matches.
    const hasError =
      fields.some((field) => field.error);
  • 22. Use every() for all-items checks.
    every() returns true only when all items pass.
    const isFormValid =
      fields.every((field) => field.valid);
  • 23. Use includes() for simple value checks.
    includes() is cleaner than checking index positions.
    const roles =
      ["admin", "editor"];
    
    const canEdit =
      roles.includes("editor");
  • 24. Avoid mutation when updating arrays.
    Prefer creating new arrays for predictable state updates.
    const updatedItems =
      items.map((item) => {
        if (item.id === 1) {
          return {
            ...item,
            selected: true
          };
        }
    
        return item;
      });
  • 25. Avoid mutation when removing array items.
    Use filter() instead of directly changing the array.
    const remainingUsers =
      users.filter(
        (user) => user.id !== removedUserId
      );
  • 26. Use modules for reusable code.
    Export reusable functions and import them where needed.
    export function formatPrice(price) {
      return "$" + price.toFixed(2);
    }
  • 27. Use named exports for multiple utilities.
    Named exports make imports clear and tree-shakeable.
    export const appVersion = "1.0.0";
    
    export function logMessage(message) {
      console.log(message);
    }
  • 28. Use default exports for one main value.
    Default exports are useful when a file has one primary purpose.
    export default function Button() {
      return "Button";
    }
  • 29. Use classes only when they improve structure.
    Classes are useful for models, services, and reusable behavior.
    class UserService {
      constructor(apiUrl) {
        this.apiUrl = apiUrl;
      }
    
      getUsers() {
        return fetch(this.apiUrl);
      }
    }
  • 30. Prefer composition over large inheritance chains.
    Keep behavior small and reusable instead of deeply nested classes.
    const canLog =
      {
        log(message) {
          console.log(message);
        }
      };
  • 31. Use Promises for async operations.
    Promises make async success and failure easier to handle.
    fetch("/api/users")
      .then((response) => response.json())
      .then((users) => console.log(users))
      .catch((error) => console.error(error));
  • 32. Use async await for readable async code.
    Async await is easier to read for multi-step async flows.
    async function loadUsers() {
      const response =
        await fetch("/api/users");
    
      return response.json();
    }
  • 33. Always handle async errors.
    Use try catch around awaited operations.
    async function loadData() {
      try {
        const response =
          await fetch("/api/data");
    
        return await response.json();
      } catch (error) {
        console.error(error.message);
        return null;
      }
    }
  • 34. Use Promise.all() for parallel requests.
    Use it when requests do not depend on each other.
    const [users, products] =
      await Promise.all(
        [
          fetch("/api/users").then((res) => res.json()),
          fetch("/api/products").then((res) => res.json())
        ]
      );
  • 35. Use Promise.allSettled() for optional tasks.
    It lets some tasks fail without stopping all results.
    const results =
      await Promise.allSettled(
        [
          fetch("/api/news"),
          fetch("/api/weather")
        ]
      );
  • 36. Avoid async work inside forEach().
    forEach() does not wait for async callbacks.
    for (const id of ids) {
      await loadUser(id);
    }
  • 37. Use for...of for sequential async tasks.
    Use it when each async step must finish before the next one starts.
    for (const file of files) {
      await uploadFile(file);
    }
  • 38. Use for await...of for async iterables.
    This is useful for streams and async generators.
    for await (const chunk of stream) {
      console.log(chunk);
    }
  • 39. Use optional chaining for safe property access.
    Optional chaining avoids errors when values are missing.
    const city =
      user?.address?.city;
  • 40. Use nullish coalescing for safe defaults.
    Use ?? when only null or undefined should use the fallback.
    const pageSize =
      settings.pageSize ?? 20;
  • 41. Prefer strict equality.
    Use === and !== to avoid type conversion surprises.
    if (status === "active") {
      showDashboard();
    }
  • 42. Use meaningful variable names.
    Clear names reduce comments and improve maintainability.
    const activeUsers =
      users.filter((user) => user.active);
  • 43. Keep functions small and focused.
    A function should do one clear task.
    function calculateTax(price, rate) {
      return price * rate;
    }
  • 44. Avoid deeply nested code.
    Return early to make conditions easier to read.
    function saveUser(user) {
      if (!user) {
        return null;
      }
    
      return api.save(user);
    }
  • 45. Use object parameters for many arguments.
    Object parameters make function calls easier to understand.
    function createUser(options) {
      const { name, email, role } =
        options;
    
      return {
        name,
        email,
        role
      };
    }
  • 46. Use Set for unique values.
    Set removes duplicate values easily.
    const tags =
      ["js", "css", "js"];
    
    const uniqueTags =
      [...new Set(tags)];
  • 47. Use Map for key-value collections.
    Map is useful when keys are dynamic or not simple strings.
    const cache =
      new Map();
    
    cache.set("users", []);
    
    const users =
      cache.get("users");
  • 48. Avoid mixing data transformation and side effects.
    Keep transformation methods pure where possible.
    const names =
      users.map((user) => user.name);
  • 49. Use comments only when they add real value.
    Prefer clear code first, comments second.
    // Retry once because this API may fail during cold start
    await retryOnce(loadReport);
  • 50. Use linting and formatting tools.
    ESLint and Prettier help keep ES6 code consistent across teams.
    const userName =
      formatUserName(user);

ES6 Best Practices Summary

  • Prefer const, use let only when reassignment is needed, and avoid var.
  • Use arrow functions for callbacks, but use regular functions for object methods and constructors.
  • Use destructuring, spread syntax, template literals, and default parameters to write cleaner code.
  • Use array methods like map(), filter(), reduce(), find(), some(), and every() for readable data operations.
  • Use async await with proper error handling for API calls and asynchronous workflows.
  • Keep code small, readable, immutable where possible, and consistent with team linting rules.

Pro Tip

In interviews, explain ES6 best practices with intent: use const for safety, arrow functions for callbacks, destructuring for readability, spread for immutable updates, array methods for data transformation, and async await for clean asynchronous code.