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.

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.
What Is ES6 and Why Does It Matter?
Before we dive into the features, let’s get one thing straight.
Why does it matter to you?
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
letwhen you need to reassign - Never use
varin 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.
// 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
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
| Feature | What It Does | Example |
|---|---|---|
let / const | Block-scoped variables | const name = "Saleem" |
| Arrow Functions | Shorter function syntax | const add = (a, b) => a + b |
| Template Literals | Embed variables in strings | `Hello, ${name}` |
| Destructuring | Unpack objects/arrays | const { name } = user |
| Default Params | Fallback function values | function greet(name = "friend") |
Spread (...) | Expand iterables | [...arr1, ...arr2] |
Rest (...) | Collect arguments | function sum(...nums) |
| Classes | OOP syntax | class Dog extends Animal |
| Modules | Import/export code | import { add } from './math' |
| Promises | Handle async operations | .then().catch() |
| Async/Await | Cleaner async syntax | await fetch(url) |
for...of | Clean iteration | for (const item of array) |
| Map / Set | New data structures | new Set([1,2,2,3]) |
| Optional Chaining | Safe property access | user?.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.
