JavaScript ES6 Features

Top JavaScript ES6 Features Every Dev Should Use

User avatar placeholder
Written by Amir58

February 24, 2026

If you’ve been writing JavaScript for a while, you’ve probably heard the term “ES6” thrown around like it’s some kind of secret club. Spoiler: it’s not. ES6 (also called ECMAScript 2015) is simply a major update to JavaScript that made the language cleaner, faster to write, and honestly — way more fun.

JavaScript ES6 Features

Think of it like upgrading from a flip phone to a smartphone. Everything works better. You wonder how you ever lived without it.

In this guide, we’re going to walk through the most important ES6 features you should know — with real examples, plain English explanations, and tips on when to actually use them. Whether you’re a beginner who just learned what a for loop is, or an intermediate dev who still writes var everywhere (we need to talk), this post is for you.

Let’s get into it.


Table of Contents

What Is ES6 and Why Does It Matter?

Before we dive into the features, let’s get one thing straight.

JavaScript is governed by a standard called ECMAScript. Every year, a committee releases a new version of this standard. ES6 was released in 2015 and was the biggest update the language had seen in years. It introduced so many useful features that developers still talk about it today.

Why does it matter to you?

Because if you’re writing modern JavaScript — or using any popular framework like React, Vue, or Angular — you’re already using ES6 whether you realize it or not. Understanding why these features exist makes you a much better developer.


1. let and const — Goodbye to var

This is where most people start, and for good reason.

Before ES6, we only had var to declare variables. And var had a quirky behavior called hoisting that could cause really sneaky bugs.

console.log(name); // undefined (not an error!)
var name = "Saleem";

That’s confusing, right? The variable exists before it’s even declared. ES6 gave us let and const to fix this mess.

What’s the Difference?

let — Use it when the value of your variable will change.

let score = 0;
score = 10; // This works fine

const — Use it when the value won’t change (constant).

const siteName = "TechBlog";
siteName = "OtherBlog"; // Error! You can't reassign a const.

Block Scoping — The Real Superpower

Both let and const are block-scoped. That means they only exist inside the {} where they’re defined.

if (true) {
  let message = "Hello";
  console.log(message); // "Hello"
}
console.log(message); // Error! message is not defined here

With var, this wouldn’t throw an error. The variable would “leak” out of the block. That’s what caused all those bugs.

Quick rule of thumb:

  • Default to const
  • Use let when you need to reassign
  • Never use var in modern code

2. Arrow Functions — Write Less, Do More

This is probably the feature you’ll use every single day.

Arrow functions are a shorter way to write functions. Instead of this:

function greet(name) {
  return "Hello, " + name;
}

You write this:

const greet = (name) => "Hello, " + name;

One line. Clean. Simple.

The Syntax Breakdown

// Traditional function
function add(a, b) {
  return a + b;
}

// Arrow function
const add = (a, b) => a + b;

// Single parameter? No parentheses needed
const double = n => n * 2;

// No parameters? Use empty parentheses
const sayHi = () => "Hi there!";

Arrow Functions and this

Here’s where it gets a bit deeper. Arrow functions don’t have their own this keyword. They inherit this from the surrounding context.

This is incredibly useful in callbacks and event handlers where the value of this used to get confusing.

// Old way — this is the problem
function Timer() {
  this.seconds = 0;
  setInterval(function() {
    this.seconds++; // "this" is wrong here!
  }, 1000);
}

// New way — arrow function fixes it
function Timer() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++; // "this" works correctly now
  }, 1000);
}

Arrow functions are great for short callbacks, array methods like .map() and .filter(), and any situation where you want concise syntax.


3. Template Literals — String Concatenation, Reinvented

Remember doing this?

var greeting = "Hello, " + firstName + "! You have " + messages + " new messages.";

That’s a mess. ES6 introduced template literals using backticks (` ) instead of quotes. Now you can embed variables directly inside your strings.

const greeting = `Hello, ${firstName}! You have ${messages} new messages.`;

So much cleaner.

Multi-line Strings

Template literals also let you write strings across multiple lines without the \n hack.

// Old way
var html = "<div>\n  <p>Hello</p>\n</div>";

// New way
const html = `
  <div>
    <p>Hello</p>
  </div>
`;

Expressions Inside Templates

You can put any JavaScript expression inside ${}.

const price = 50;
const tax = 0.1;

console.log(`Total: $${price + price * tax}`); // Total: $55

This works with function calls, ternary operators, math — anything.


4. Destructuring — Unpack Arrays and Objects Like a Pro

Destructuring is one of those features that feels like magic the first time you use it.

It lets you pull values out of arrays or objects and assign them to variables in a single line.

Object Destructuring

// Old way
const user = { name: "Saleem", age: 30, city: "Lahore" };
const name = user.name;
const age = user.age;

// New way — destructuring
const { name, age, city } = user;
console.log(name); // "Saleem"
console.log(city); // "Lahore"

You can also rename variables while destructuring:

const { name: userName, city: userCity } = user;
console.log(userName); // "Saleem"

And set default values:

const { name, country = "Pakistan" } = user;
console.log(country); // "Pakistan" (since user doesn't have a country property)

Array Destructuring

const colors = ["red", "green", "blue"];

const [first, second, third] = colors;
console.log(first);  // "red"
console.log(second); // "green"

Skip elements you don’t need:

const [, , lastColor] = colors;
console.log(lastColor); // "blue"

Why It’s Useful in Real Life

Destructuring is everywhere in modern code. You’ll see it constantly in React:

function UserCard({ name, age, city }) {
  return `${name}, ${age}, from ${city}`;
}

That { name, age, city } is destructuring happening right in the function parameters.


5. Default Parameters — No More undefined Headaches

Before ES6, handling missing function arguments was annoying:

function greet(name) {
  name = name || "stranger";
  return "Hello, " + name;
}

With ES6, you set defaults right in the function signature:

function greet(name = "stranger") {
  return `Hello, ${name}!`;
}

greet("Saleem"); // "Hello, Saleem!"
greet();         // "Hello, stranger!"

Clean, readable, and obvious to anyone reading your code.

You can use expressions and even other parameters as defaults:

function createUser(name, role = "viewer", id = Date.now()) {
  return { name, role, id };
}

6. Spread and Rest Operators — The Three Dots You’ll Love

Both spread and rest use the same ... syntax, but they do opposite things. Stick with me — this one’s worth understanding properly.

The Spread Operator — Expand Things Out

Spread takes an array (or object) and “spreads” its elements out.

const fruits = ["apple", "banana"];
const moreFruits = ["mango", ...fruits, "grape"];
// ["mango", "apple", "banana", "grape"]

Copying an array without reference issues:

const original = [1, 2, 3];
const copy = [...original];
copy.push(4);

console.log(original); // [1, 2, 3] — unaffected
console.log(copy);     // [1, 2, 3, 4]

Merging objects:

const defaults = { theme: "dark", fontSize: 14 };
const userSettings = { fontSize: 18, language: "en" };

const finalSettings = { ...defaults, ...userSettings };
// { theme: "dark", fontSize: 18, language: "en" }
// userSettings values overwrite defaults

The Rest Operator — Gather Things Together

Rest collects multiple arguments into a single array. You’ll use it in function parameters.

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

sum(1, 2, 3);       // 6
sum(10, 20, 30, 40); // 100

You can also use rest with destructuring to grab “everything else”:

const [first, ...remaining] = [1, 2, 3, 4, 5];
console.log(first);     // 1
console.log(remaining); // [2, 3, 4, 5]

7. Enhanced Object Literals — Less Typing, Same Result

ES6 made creating objects a lot less repetitive.

Shorthand Properties

const name = "Saleem";
const age = 30;

// Old way
const user = { name: name, age: age };

// New way
const user = { name, age }; // Same thing!

Shorthand Methods

// Old way
const calculator = {
  add: function(a, b) { return a + b; }
};

// New way
const calculator = {
  add(a, b) { return a + b; }
};

Computed Property Names

You can now use dynamic expressions as property keys:

const field = "email";
const user = {
  [field]: "saleem@example.com"
};
// { email: "saleem@example.com" }

This is incredibly useful when building objects dynamically.


8. Classes — Object-Oriented JavaScript, Made Cleaner

ES6 introduced a class syntax that makes object-oriented programming much more approachable.

Under the hood, JavaScript still uses prototypes — classes are just cleaner syntax on top. But the syntax is far more readable.

class Animal {
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }

  speak() {
    return `${this.name} says ${this.sound}!`;
  }
}

const dog = new Animal("Rex", "Woof");
console.log(dog.speak()); // "Rex says Woof!"

Inheritance with extends

class Dog extends Animal {
  constructor(name) {
    super(name, "Woof"); // calls the parent constructor
  }

  fetch() {
    return `${this.name} fetches the ball!`;
  }
}

const myDog = new Dog("Buddy");
console.log(myDog.speak());  // "Buddy says Woof!"
console.log(myDog.fetch());  // "Buddy fetches the ball!"

Classes are used heavily in React (especially older class components), and understanding them helps you read a lot of existing codebases.


9. Modules — import and export

Before ES6, JavaScript had no native module system. Developers used workarounds like CommonJS (require) or just dumped everything into one giant file.

ES6 modules let you split your code into separate files and import only what you need.

Exporting

// math.js
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export default function multiply(a, b) {
  return a * b;
}

Importing

// app.js
import multiply, { PI, add } from './math.js';

console.log(PI);           // 3.14159
console.log(add(2, 3));    // 5
console.log(multiply(4, 5)); // 20

Named exports (with {}) let you export multiple things. Default exports (without {}) are the main export of a file.

You’ll see this pattern in every modern React, Vue, or Node project.


10. Promises — Handling Asynchronous Code Gracefully

JavaScript is single-threaded, which means it handles one thing at a time. But a lot of things — fetching data, reading files, waiting for timers — take time. That’s where async programming comes in.

Before Promises, developers used nested callbacks, which led to the infamous callback hell:

getData(function(data) {
  processData(data, function(result) {
    saveResult(result, function(response) {
      // three levels deep... it gets worse
    });
  });
});

Promises clean this up significantly.

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

A Promise represents a value that will be available in the future — either it resolves (succeeds) or rejects (fails).

Creating Your Own Promise

const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));

wait(2000).then(() => console.log("Two seconds passed!"));

11. Async/Await — Promises, But Even Cleaner

ES2017 (built on ES6’s foundation) brought us async/await, which makes asynchronous code look almost synchronous.

async function fetchUserData() {
  try {
    const response = await fetch("https://api.example.com/user");
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Something went wrong:", error);
  }
}

await pauses execution inside an async function until the Promise resolves. The try/catch handles errors cleanly.

This is now the standard way to handle async operations in modern JavaScript.


12. for...of Loop — Iterate Smarter

The for...of loop gives you a clean way to iterate over any iterable — arrays, strings, Maps, Sets.

const languages = ["JavaScript", "Python", "Rust"];

for (const lang of languages) {
  console.log(lang);
}
// JavaScript
// Python
// Rust

Compare that to a regular for loop:

for (let i = 0; i < languages.length; i++) {
  console.log(languages[i]);
}

Same result, more typing, harder to read.

For objects, use for...in or Object.entries():

const user = { name: "Saleem", role: "SEO Lead" };

for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}
// name: Saleem
// role: SEO Lead

13. Map and Set — New Data Structures

ES6 introduced two new data structures that solve specific problems really well.

Set — Unique Values Only

A Set is like an array, but it only stores unique values.

const numbers = new Set([1, 2, 3, 3, 4, 4, 4]);
console.log(numbers); // Set {1, 2, 3, 4}

This is the cleanest way to remove duplicates from an array:

const arr = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(arr)];
// [1, 2, 3, 4, 5]

Map — Better Key-Value Pairs

A Map is like an object, but keys can be any type — not just strings.

const userMap = new Map();
userMap.set("name", "Saleem");
userMap.set("age", 30);
userMap.set(true, "admin");

console.log(userMap.get("name")); // "Saleem"
console.log(userMap.get(true));   // "admin"

Maps also maintain insertion order and have a handy .size property.


14. Symbol — Unique Identifiers

Symbol creates a completely unique value every time you call it. Even two symbols with the same description are not equal.

const id1 = Symbol("id");
const id2 = Symbol("id");

console.log(id1 === id2); // false

Symbols are mainly used as unique property keys to avoid naming conflicts in large codebases or libraries.

const USER_ID = Symbol("userId");
const user = {
  [USER_ID]: 12345,
  name: "Saleem"
};

You won’t use this every day, but knowing it exists helps when reading library source code.


15. Optional Chaining and Nullish Coalescing (ES2020 — Worth Knowing)

These two features aren’t ES6 technically, but they’re so closely related to modern JavaScript that they belong in this conversation.

Optional Chaining (?.)

Safely access nested properties without errors if something is null or undefined.

const user = {
  profile: {
    address: {
      city: "Lahore"
    }
  }
};

// Old way — verbose
const city = user && user.profile && user.profile.address && user.profile.address.city;

// New way
const city = user?.profile?.address?.city;
// "Lahore" — or undefined if any part is missing

Nullish Coalescing (??)

Returns the right side only when the left side is null or undefined (unlike || which also triggers on 0, false, or empty string).

const score = 0;
console.log(score || 10);  // 10 (wrong! score is 0, not null)
console.log(score ?? 10);  // 0 (correct! 0 is a valid value)

Putting It All Together — A Real Example

Let’s write a small function using multiple ES6 features at once:

const fetchUserProfile = async (userId, options = {}) => {
  const { includeAddress = false, timeout = 5000 } = options;

  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    const { name, email, address } = await response.json();

    const profile = {
      name,
      email,
      ...(includeAddress && { address })
    };

    return profile;

  } catch (error) {
    console.error(`Failed to fetch user ${userId}:`, error);
    return null;
  }
};

// Usage
fetchUserProfile(42, { includeAddress: true })
  .then(user => console.log(user?.name ?? "Unknown user"));

In just a few lines, we used: arrow functions, async/await, template literals, destructuring, default parameters, spread operator, optional chaining, and nullish coalescing. That’s the power of ES6+.


Quick Reference Cheat Sheet

FeatureWhat It DoesExample
let / constBlock-scoped variablesconst name = "Saleem"
Arrow FunctionsShorter function syntaxconst add = (a, b) => a + b
Template LiteralsEmbed variables in strings`Hello, ${name}`
DestructuringUnpack objects/arraysconst { name } = user
Default ParamsFallback function valuesfunction greet(name = "friend")
Spread (...)Expand iterables[...arr1, ...arr2]
Rest (...)Collect argumentsfunction sum(...nums)
ClassesOOP syntaxclass Dog extends Animal
ModulesImport/export codeimport { add } from './math'
PromisesHandle async operations.then().catch()
Async/AwaitCleaner async syntaxawait fetch(url)
for...ofClean iterationfor (const item of array)
Map / SetNew data structuresnew Set([1,2,2,3])
Optional ChainingSafe property accessuser?.address?.city

Frequently Asked Questions (FAQs)

Q1: Is ES6 the same as JavaScript?

No, ES6 is a version of the ECMAScript standard that JavaScript follows. Think of ECMAScript as the rulebook and JavaScript as the language that follows those rules. ES6 refers to the 2015 edition of those rules.

Q2: Do I need to learn ES6 before React or Vue?

Yes, strongly recommended. React and Vue are built using ES6 features. If you don’t understand arrow functions, modules, destructuring, and classes, a lot of framework code will look like gibberish.

Q3: Are all ES6 features supported in all browsers?

Most modern browsers (Chrome, Firefox, Safari, Edge) fully support ES6. For older browsers like IE11, you’d use a tool called Babel to convert your ES6 code into older JavaScript.

Q4: What’s the difference between let and const?

let allows reassignment; const doesn’t. Both are block-scoped, which makes them safer than var.

Q5: Should I use Promises or async/await?

Both do the same job, but async/await is generally cleaner and easier to read. Under the hood, async/await is just Promises with nicer syntax. In most cases, prefer async/await.

Q6: What is the spread operator used for?

The spread operator (...) expands an array or object. It’s commonly used to copy arrays, merge objects, and pass array items as individual function arguments.

Q7: Is class in JavaScript the same as in Java or Python?

Conceptually similar, but JavaScript classes are built on prototypal inheritance under the hood, not classical inheritance. The syntax looks similar to Java/Python, but the behavior has some differences worth studying as you advance.

Q8: How do I practice ES6 features?

The best way is to open your browser’s developer console (F12) and start writing code. You can also use tools like CodePen, JSFiddle, or StackBlitz to experiment without any setup.


Conclusion

ES6 wasn’t just an update — it was a transformation. It took JavaScript from a language full of sharp edges and quirky behavior and turned it into something genuinely enjoyable to write.

If you take nothing else from this guide, remember these five things that will immediately improve your code:

Use const and let instead of var. Write arrow functions for cleaner callbacks. Use template literals instead of string concatenation. Destructure objects and arrays whenever you can. Learn async/await — it’s the future of JS async code.

You don’t have to memorize every feature at once. Pick one, use it in your next project, then add another. That’s how real learning happens.

The best JavaScript developers aren’t the ones who memorized the spec — they’re the ones who understand why these features exist and when to reach for them. You’re well on your way.

Image placeholder

Lorem ipsum amet elit morbi dolor tortor. Vivamus eget mollis nostra ullam corper. Pharetra torquent auctor metus felis nibh velit. Natoque tellus semper taciti nostra. Semper pharetra montes habitant congue integer magnis.

Leave a Comment