Last updated: 2026-05-05

ES6 features

ES6 (ECMAScript 2015) features were major upgrades that made JavaScript cleaner, safer, and more powerful. These features modernized the language by addressing long-standing issues like scope leakage and "callback hell."


Block Scope: let and const

Before ES6, all variables used var, which is function-scoped. This often led to bugs where variables "leaked" out of if statements or loops. let and const are block-scoped, meaning they only exist within the curly braces {} where they are defined.

FeaturePre-ES6 (var)Post-ES6 (let/const)
ScopeFunctionBlock
LeakageLeaks out of if/for blocksContained within the block
Accessed before declarationReturns undefined silentlyThrows ReferenceError

Example:

if (true) {
  let name = "Wariz";
  const age = 17;
}
console.log(name); // ❌ ReferenceError: name is not defined

Arrow Functions () => {}

Arrow functions provide a shorter syntax for writing functions. They allow for concise one-liners and handle the this keyword differently than traditional functions.

  • Before ES6:
    function greet(name) {
      return "Hello " + name;
    }
    
  • ES6 Arrow Function:
    const greet = name => `Hello ${name}`;
    

Template Literals

Template literals replaced messy string concatenation (using +) with backticks (`) and placeholders (${}). This makes building strings much more readable.

const name = "Wariz";
// With ES6 Template Literals
const greeting = `Hello ${name}!`; 
console.log(greeting); // Hello Wariz!

Destructuring

Destructuring allows you to "unpack" values from arrays or properties from objects into distinct variables instantly.

  • Object Destructuring:
    const user = { name: "Wariz", age: 17 };
    const { name, age } = user;
    
  • Array Destructuring:
    const numbers = [10, 20, 30];
    const [first, second] = numbers;
    

Rest and Spread Operators (...)

While they use the same syntax (...), they serve opposite purposes:

1. Rest Operator

Collects multiple individual elements into a single array. Great for functions with unknown numbers of arguments.

function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}

2. Spread Operator

Expands or "spreads" an array or object into a new one.

const nums = [1, 2, 3];
const newNums = [...nums, 4, 5]; // [1, 2, 3, 4, 5]

Modules (import / export)

Modules allow you to organize code into independent, reusable files. Instead of one massive file, you can export specific pieces of code from one file and import them into another.

  • Exporting (math.js): export const add = (a, b) => a + b;
  • Importing (app.js): import { add } from './math.js';

Promises and async/await

Promises were introduced to handle Asynchronous JavaScript. Before Promises, developers used callbacks, which often led to "callback hell" (nested, unreadable code).

A Promise represents a task that will finish in the future. It can be in one of three states:

  1. Pending: Initial state.
  2. Resolved (Fulfilled): The task completed successfully.
  3. Rejected: The task failed.
const fetchData = new Promise((resolve, reject) => {
  const success = true;
  if (success) {
    resolve("Data loaded");
  } else {
    reject("Error occurred");
  }
});

fetchData
  .then(result => console.log(result)) // Runs on success
  .catch(error => console.log(error)); // Runs on failure

async/await

While Promises solved callback hell, chaining multiple .then() calls can still become difficult to read. async/await is the modern syntax built on top of Promises that makes asynchronous code read more like synchronous code — cleaner and easier to follow.

  • async — Marks a function as asynchronous. An async function always returns a Promise.
  • await — Pauses execution inside an async function until the Promise resolves, then returns the result.
async function loadData() {
  try {
    const result = await fetchData;
    console.log(result); // "Data loaded"
  } catch (error) {
    console.log(error); // Runs if the Promise is rejected
  }
}

loadData();

The try/catch block replaces .then() and .catch()try handles the success case and catch handles the failure. All three approaches (callbacks → Promises → async/await) exist in JavaScript today, which is why you will encounter all of them in real codebases.