Skip to content
JavaScriptFrontendInterview

JavaScript ES6 Tutorial: Every Feature, and What It Replaced

October 31, 2024 Updated September 29, 2026 7 min read

ES6 is the release that made JavaScript pleasant to write. This JavaScript ES6 tutorial covers every feature it introduced, and — more usefully — what each one replaced, because the ES6 syntax makes far more sense once you have seen the mess it was designed to fix.

What is ES6, exactly?

ES6 is ECMAScript 2015. Both names are correct and the confusion is TC39’s fault: the committee shipped ES5 in 2009, spent six years on the next release, and then switched to naming versions by year. So ES6 and ES2015 are the same thing, and everything after it is ES2016, ES2017 and so on.

In practice, when someone says “ES6” today they usually mean modern ES6 JavaScript generally, not the 2015 release specifically. Every browser still in use supports all of it.

let and const

Before ES6 there was only var, which had two problems: it ignored blocks, and it could be redeclared silently.

Code
// Before — var leaks out of the block
for (var i = 0; i < 3; i++) { /* ... */ }
console.log(i);          // 3 — still here

// After — let stays where it was declared
for (let j = 0; j < 3; j++) { /* ... */ }
console.log(j);          // ReferenceError

Use const by default and let only when the value is reassigned. var has no remaining use.

const does not mean immutable

This is the most common misunderstanding of the two. const stops the binding being reassigned; it does nothing to the value:

Code
const user = { name: 'Asha' };
user.name = 'Ravi';      // fine — the object is not frozen
user = {};               // TypeError — the binding is

// If you want the object frozen too:
const frozen = Object.freeze({ name: 'Asha' });

Arrow functions

Code
// Before
var double = function (n) { return n * 2; };

// After
const double = n => n * 2;
const add = (a, b) => a + b;

// Returning an object needs parentheses, or the braces
// are read as a function body:
const make = name => ({ name });

Arrow functions and this — the real reason they exist

Arrow functions are not just shorter. They do not have their own this; they use the one from where they were written. That single difference removed an entire category of bug:

Code
// Before — this is wrong inside the callback
function Timer() {
  this.seconds = 0;
  var self = this;                       // the workaround everyone wrote
  setInterval(function () {
    self.seconds++;
  }, 1000);
}

// After — this is the Timer, because the arrow did not rebind it
function Timer() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++;
  }, 1000);
}

The flip side: never use an arrow function as an object method or a constructor. It will not have the this you expect.

Template literals

Code
// Before
var greeting = 'Hello ' + name + ', you have ' + count + ' messages';

// After
const greeting = `Hello ${name}, you have ${count} messages`;

// And multi-line strings, which needed \n before:
const html = `
  <div>
    <h1>${title}</h1>
  </div>
`;

Destructuring

Pulling values out of objects and arrays, with defaults and renaming built in. Probably the ES6 feature you will use most often.

Code
const response = { name: 'Asha', role: 'admin' };

// Basic
const { name, role } = response;

// With a default for a key that is missing
const { name, city = 'Delhi' } = response;

// Renaming, for when the API's names are not yours
const { name: userName } = response;

// Arrays, by position — the gap skips one
const [first, , third] = [1, 2, 3];

// Straight in a function signature
function greet({ name, role = 'guest' }) {
  return `${name} (${role})`;
}

One catch worth knowing: a default only applies when the value is undefined, not when it is null. An API returning null will sail past your default and give you null.

Spread and rest

Same three dots, opposite jobs. Spread pulls a thing apart; rest gathers things up.

Code
// Spread — copying and merging
const copy   = [ ...original ];
const joined = [ ...first, ...second ];
const merged = { ...defaults, ...overrides };   // later wins

// Rest — collecting the remainder
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}

const { id, ...everythingElse } = user;

A warning that costs people hours: spread makes a shallow copy. Nested objects are still shared with the original, so changing one changes both.

Default parameters

Code
// Before
function connect(host, port) {
  port = port || 5432;        // breaks if you pass 0
  // ...
}

// After
function connect(host, port = 5432) { /* ... */ }

The old || trick treats 0, '' and false as missing. A real default parameter only fires on undefined.

Classes

Syntax over the prototype system that was already there. It does not add a new object model — it makes the existing one readable.

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

  speak() {
    return `${this.name} makes a sound`;
  }

  static create(name) {
    return new Animal(name);
  }
}

class Dog extends Animal {
  speak() {
    return `${this.name} barks`;
  }
}

Modules: import and export

Code
// maths.js
export const PI = 3.14159;
export function area(r) { return PI * r * r; }
export default function circumference(r) { return 2 * PI * r; }

// app.js
import circumference, { PI, area } from './maths.js';
import * as maths from './maths.js';

One default per file, as many named exports as you like. In the browser this needs <script type="module">; in Node it needs "type": "module" in package.json or an .mjs extension.

Promises

The replacement for nested callbacks. Worth learning properly, because async/await — which came later, in ES2017 — is built directly on them.

Code
const request = new Promise((resolve, reject) => {
  if (ok) { resolve(data); } else { reject(new Error('failed')); }
});

request
  .then(data => process(data))
  .catch(error => console.error(error))
  .finally(() => stopSpinner());

// Several at once
const [users, posts] = await Promise.all([ getUsers(), getPosts() ]);

Map and Set

Code
// Set — unique values
const seen = new Set([1, 2, 2, 3]);   // {1, 2, 3}
const unique = [ ...new Set(list) ];  // the one-line dedupe

// Map — keys can be anything, and order is kept
const byId = new Map();
byId.set(userObject, 'metadata');     // an object as a key
byId.get(userObject);

Use a Map over a plain object when keys are not strings, when insertion order matters, or when you need .size without counting.

for…of

Code
const list = ['a', 'b', 'c'];

for (const item of list) { }          // values — usually what you want
for (const key in list) { }           // keys, and inherited ones too
list.forEach(item => { });            // no break, no await

for...of is the one that supports break, continue and await inside the loop. forEach supports none of them.

Symbols and generators

The two ES6 features you can safely learn last. Symbols are unique keys that will not collide with anyone else’s; generators are functions that pause and resume.

Code
const id = Symbol('id');
user[id] = 123;              // invisible to Object.keys

function* counter() {
  let i = 0;
  while (i < 3) { yield i++; }
}

for (const n of counter()) { }   // 0, 1, 2

You will meet both more often through libraries than in code you write yourself.

What came after ES6

ES6 is the large one, but the yearly releases since have added things you will use just as much:

  • ES2016 — the exponent operator ** and Array.includes()
  • ES2017 — async/await, which most people think of as ES6 and is not
  • ES2018 — object spread, which is also often assumed to be ES6
  • ES2019 — Array.flat(), Object.fromEntries()
  • ES2020 — optional chaining ?. and nullish coalescing ??
  • ES2021 and later — replaceAll(), logical assignment, top-level await

Optional chaining and nullish coalescing in particular are worth adding to your habits straight after ES6. They remove more code than most of ES6 did.

Common questions

Is ES6 the same as ES2015?

Yes, exactly the same release. ES6 was the working name; ES2015 is the published one. Both are used interchangeably.

Do I still need Babel for ES6?

No. Every browser in current use supports all of ES6 natively. Babel is still worth having for features newer than about ES2022, or if you must support a browser that stopped updating years ago.

In what order should I learn ES6?

Start with four ES6 concepts: const and let, arrow functions, template literals, and destructuring. Those appear in nearly every line of modern JavaScript. Classes, modules and promises follow. Symbols and generators can wait.

Is var completely dead?

For new code, yes. You will still meet it in older codebases, and it is worth understanding why it behaves as it does — but there is no situation where var is the better choice today.

Views: 629

TheEasyMaster

Author at The Easy Master.

Previous
Master HTML: Learn to Build Web Pages
Next
Best Free APIs for Developers 2026: 12 Verified, No Sign-Up

Related posts

JavaScript Cheat Sheet: Syntax, Examples & JS Concepts in Hindi

The tutorial teaches it; the cheat sheet is for when you just need reminding.

Leave a Reply

Your email address will not be published. Required fields are marked *