ES2015 established block-scoped bindings, arrows, classes, modules, Promises, iterables, generators, Map, Set, Symbol, destructuring, and other foundations of modern JavaScript.
Use these features through their actual contracts: lexical capture is not dynamic this, collection copies are shallow, module boundaries are strict, and iterable or keyed data structures preserve distinct semantics.
ES6, standardized as ECMAScript 2015, provides `let`, `const`, arrow functions, template literals, destructuring, classes, modules, promises, iterators, generators, maps, sets, symbols, and rest/spread syntax. Check each feature against the minimum runtime and build target.
Most modern JavaScript code uses ES6 every day. If you work with React, Vue, Angular, Node.js, npm packages, bundlers, or browser APIs, you will constantly see let, const, arrow functions, template literals, destructuring, classes, modules, promises, and spread/rest syntax.
The table below summarizes the most important ES6 features and why they matter.
| Feature | Purpose | Example |
|---|---|---|
| let, const | Block-scoped variable declarations | const name = "Asha"; |
| Template literals | String interpolation and multi-line strings | `Hello ${name}` |
| Arrow functions | Shorter function syntax with lexical this | n => n * 2 |
| Default parameters | Fallback values for function arguments | function greet(name = "Guest") |
| Rest and spread | Collect or expand values | sum(...numbers) |
| Destructuring | Extract array/object values into variables | const { id } = user; |
| Enhanced objects | Shorter object property and method syntax | { name, login() {} } |
| Classes | Cleaner syntax for constructor/prototype patterns | class User {} |
| Modules | Import and export code between files | export default App; |
| Promises | Handle asynchronous results | fetch(url).then(...) |
Before ES6, JavaScript mainly used var. A var variable is function-scoped, which can create confusing bugs inside blocks and loops. ES6 added let and const, which are block-scoped. A block is the area inside curly braces, such as an if statement, loop, or function body.
Use const by default when the variable should not be reassigned. Use let when the value needs to change. Avoid var in modern JavaScript unless you are maintaining older code.
const prevents reassignment, but it does not make objects or arrays immutable. You can still change the contents of an object declared with const.
const course = "JavaScript ES6";
let lesson = 1;
if (lesson === 1) {
const topic = "let and const";
lesson = lesson + 1;
console.log(topic); // let and const
}
console.log(course); // JavaScript ES6
console.log(lesson); // 2
// console.log(topic); // ReferenceError: topic is not defined
const user = {
name: "Riya",
role: "Student"
};
user.role = "Developer"; // allowed
user.city = "Pune"; // allowed
// user = {}; // TypeError: Assignment to constant variable
Template literals use backticks instead of single or double quotes. They support interpolation with ${...}, multi-line strings, and readable dynamic text. They are very useful for messages, HTML snippets, logs, and generated URLs.
const name = "Kabir";
const score = 92;
const message = `Hello ${name}, your score is ${score}%.`;
console.log(message);
const card = `
<article class="student-card">
<h2>${name}</h2>
<p>Score: ${score}%</p>
</article>
`;
Arrow functions give JavaScript a compact way to write functions. They are common in callbacks, array methods, promises, and component code. If an arrow function has one parameter, parentheses are optional. If the body is a single expression, the result is returned automatically.
Arrow functions do not create their own this. Instead, they use this from the surrounding scope. This is useful in many callbacks, but arrow functions are usually not the right choice for object methods that need their own receiver.
const double = number => number * 2;
const add = (a, b) => a + b;
const greet = name => {
const message = `Welcome, ${name}`;
return message;
};
console.log(double(6)); // 12
console.log(add(10, 20)); // 30
console.log(greet("Maya"));
const timer = {
seconds: 0,
start() {
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
};
// timer.start();
Default parameters let you define fallback values directly in the function signature. This avoids extra checks inside the function body and makes intent clear.
function createUser(name = "Guest", role = "Reader") {
return {
name,
role,
active: true
};
}
console.log(createUser());
console.log(createUser("Anika", "Admin"));
The rest parameter syntax collects remaining function arguments into an array. It is useful when a function can receive any number of values.
Spread syntax also uses three dots, but it expands arrays or objects instead of collecting values. Use it to copy arrays, merge arrays, pass array values as arguments, or build new objects without mutating the original.
const frontend = ["HTML", "CSS"];
const javascript = ["JavaScript", "TypeScript"];
const skills = [...frontend, ...javascript];
console.log(skills);
const user = { name: "Ishaan", role: "Student" };
const updatedUser = { ...user, role: "Developer", active: true };
console.log(updatedUser);
Destructuring extracts values from arrays or objects and assigns them to variables. Array destructuring is based on position.
const colors = ["red", "green", "blue"];
const [primary, secondary] = colors;
console.log(primary); // red
console.log(secondary); // green
const [first, , third] = colors;
console.log(third); // blue
Object destructuring extracts values by property name. It is heavily used with function parameters, configuration objects, API responses, and imports from libraries.
const student = {
id: 101,
name: "Nisha",
address: {
city: "Hyderabad"
}
};
const { id, name } = student;
const { address: { city } } = student;
console.log(id, name, city);
function printProfile({ name, role = "User" }) {
console.log(`${name} works as ${role}`);
}
printProfile({ name: "Dev", role: "Frontend Engineer" });
printProfile({ name: "Sana" });
ES6 improved object literal syntax. If the variable name and property name are the same, you can write the name once. You can also write methods without the function keyword and create dynamic property names with computed keys.
const name = "Task Manager";
const version = "1.0";
const dynamicKey = "createdAt";
const app = {
name,
version,
[dynamicKey]: new Date().toISOString(),
start() {
console.log(`${this.name} started`);
}
};
app.start();
The for...of loop iterates over iterable values such as arrays, strings, maps, sets, and NodeLists. It gives you the value directly, unlike for...in, which iterates over property keys.
const languages = ["JavaScript", "Python", "PHP"];
for (const language of languages) {
console.log(language);
}
for (const letter of "ES6") {
console.log(letter);
}
ES6 introduced new collection types. A Map stores key-value pairs and allows any value to be used as a key. A Set stores unique values and automatically removes duplicates.
const scores = new Map();
scores.set("Aarav", 95);
scores.set("Meera", 88);
console.log(scores.get("Aarav")); // 95
console.log(scores.has("Meera")); // true
const tags = new Set(["js", "web", "js", "frontend"]);
console.log([...tags]); // ["js", "web", "frontend"]
ES6 classes provide a clean syntax for creating objects with shared behavior. A class is syntactic sugar over JavaScript's prototype system. It does not replace prototypes; it makes constructor functions and prototype methods easier to read.
Classes can also inherit from other classes using extends and call the parent constructor with super().
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
login() {
return `${this.name} logged in`;
}
}
const user = new User("Tara", "tara@example.com");
console.log(user.login());
class Admin extends User {
constructor(name, email, permissions) {
super(name, email);
this.permissions = permissions;
}
canEdit() {
return this.permissions.includes("edit");
}
}
const admin = new Admin("Rohan", "rohan@example.com", ["edit", "delete"]);
console.log(admin.login());
console.log(admin.canEdit());
ES6 modules let you split code into separate files. A module can export variables, functions, classes, or a default value. Other files can import only what they need. Modules make code easier to organize and help avoid global variables.
// math.js
export const pi = 3.14159;
export function square(number) {
return number * number;
}
// app.js
import { pi, square } from "./math.js";
console.log(pi);
console.log(square(5));
// logger.js
export default function log(message) {
console.log(`[App] ${message}`);
}
// app.js
import log from "./logger.js";
log("Application started");
A Promise represents the eventual result of an asynchronous operation. It starts in the pending state, then becomes fulfilled or rejected. Promises replaced many callback-heavy patterns and became the foundation for modern async JavaScript.
const loadData = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Data loaded");
} else {
reject(new Error("Failed to load data"));
}
});
loadData
.then(result => console.log(result))
.catch(error => console.error(error.message));
ES6 also added helpful methods to strings and arrays. These methods make everyday checks and transformations cleaner.
const title = "JavaScript ES6 Tutorial";
console.log(title.startsWith("JavaScript")); // true
console.log(title.endsWith("Tutorial")); // true
console.log(title.includes("ES6")); // true
console.log("Hi ".repeat(3)); // Hi Hi Hi
const numbers = [5, 12, 8, 130, 44];
const firstLarge = numbers.find(number => number > 10);
const firstLargeIndex = numbers.findIndex(number => number > 10);
console.log(firstLarge); // 12
console.log(firstLargeIndex); // 1
console.log(Array.from("ES6")); // ["E", "S", "6"]
A Symbol is a unique primitive value. Symbols are often used as special object keys when you want to avoid accidental name conflicts.
const id = Symbol("id");
const user = {
name: "Leena",
[id]: 123
};
console.log(user[id]); // 123
console.log(Symbol("id") === Symbol("id")); // false
Generator functions can pause and resume execution with yield. They return an iterator, which means you can request values one at a time. Generators are useful for custom iteration, lazy sequences, and advanced async patterns.
The following example combines several ES6 features in one practical piece of code: const, default parameters, destructuring, template literals, arrow functions, spread syntax, and array methods.
const students = [
{ name: "Asha", marks: 82 },
{ name: "Vikram", marks: 74 },
{ name: "Neha", marks: 91 }
];
const addGrade = ({ name, marks }, passingMarks = 75) => ({
name,
marks,
status: marks >= passingMarks ? "Passed" : "Needs Practice"
});
const result = students.map(student => addGrade(student));
const topStudent = result.find(({ marks }) => marks > 90);
console.log([...result]);
console.log(`${topStudent.name} scored above 90 marks.`);
ES6 changed JavaScript from a small scripting language into a much more expressive language for serious application development. Once you understand let, const, arrow functions, template literals, destructuring, spread/rest syntax, classes, modules, and promises, most modern JavaScript code becomes much easier to read.
The best way to master ES6 is to use these features together. Start by replacing old var declarations, practice array and object destructuring, write small modules, and build simple examples with promises. These patterns appear everywhere in modern frontend and backend JavaScript.
ES2015 standardized iterable and iterator protocols around `Symbol.iterator`. Arrays, strings, maps, and sets can be consumed with `for...of`, spread, destructuring, and APIs that accept iterables. An iterator produces `{ value, done }`; an iterable supplies a fresh iterator according to its repeatability contract.
Generator functions implement iterators with `yield`, preserving local execution state between steps. They can delegate with `yield*` and receive values from `next`. Use them for lazy traversal and stateful protocols, and define cleanup when iteration stops before completion.
Spread is context-specific: iterable spread builds arrays or arguments, while object spread uses enumerable own properties under later semantics. Large argument spread can exceed limits, and all common spreading is shallow.
Map accepts keys of any value and preserves insertion order; Set stores unique values using SameValueZero semantics. WeakMap and WeakSet hold weakly referenced object keys and are not enumerable, making them suitable for associated metadata rather than general caches that need listing.
Symbols are unique primitive property keys. Well-known symbols customize protocols such as iteration and type conversion. Symbols reduce accidental name collisions but do not create security or privacy; reflection APIs can still discover symbol-keyed properties.
Choose Map for dynamic keyed collections, Set for membership, objects for record-like fixed fields, and arrays for ordered sequences. Converting among them can change key types, duplicates, and ordering, so test the data contract rather than choosing by syntax brevity.
ES6 is a major part of modern JavaScript, but modern JavaScript also includes newer yearly ECMAScript updates such as async/await, optional chaining, nullish coalescing, and more.
Use <code>const</code> by default. Use <code>let</code> when a variable must be reassigned. Avoid <code>var</code> in new code because its function scope and hoisting behavior can be confusing.
JavaScript classes are syntax built on top of prototypes. They provide a familiar class-style structure, but JavaScript still uses prototypal inheritance internally.
Explore 500+ free tutorials across 20+ languages and frameworks.