ES6 (ECMAScript 2015) was a landmark update to JavaScript that modernized the language. Since then, new features have been added every year. If you're still writing JavaScript the old way — with var, callback hell, and verbose object syntax — this guide will show you the modern features that make your code cleaner, safer, and more readable.

1. let and const (Forget var)

var has function scope and gets hoisted, which causes confusing bugs. Use let for variables that change and const for values that don't.

// Old way
var count = 0;
var name = "Rahul";

// Modern way
let count = 0;        // can be reassigned
const name = "Rahul"; // cannot be reassigned

// const with objects/arrays - the reference is constant, not the contents
const user = { name: "Priya", age: 22 };
user.age = 23; // This is fine
user = {};     // This throws an error

2. Arrow Functions

Arrow functions are shorter and don't have their own this binding (which fixes many bugs in callbacks).

// Old way
function add(a, b) { return a + b; }
const double = function(n) { return n * 2; };

// Arrow functions
const add = (a, b) => a + b;
const double = n => n * 2;
const greet = name => `Hello, ${name}!`;

// Multi-line arrow function
const processUser = (user) => {
  const fullName = `${user.first} ${user.last}`;
  return fullName.toUpperCase();
};

// In array methods
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);       // [2, 4, 6, 8, 10]
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15

3. Template Literals

Template literals use backticks and allow embedded expressions and multi-line strings.

const name = "Priya";
const score = 95;

// Old way
const msg = "Hello, " + name + "! Your score is " + score + ".";

// Template literal
const msg = `Hello, ${name}! Your score is ${score}.`;

// Multi-line string
const html = `
  <div class="card">
    <h2>${name}</h2>
    <p>Score: ${score}</p>
  </div>
`;

// Expression in template
const result = `${score >= 90 ? 'Excellent' : 'Good'} work!`;

4. Destructuring

Destructuring lets you extract values from arrays and objects into variables cleanly.

// Object destructuring
const user = { name: "Amit", age: 21, city: "Mumbai" };

// Old way
const name = user.name;
const age = user.age;

// Destructuring
const { name, age, city } = user;

// With rename
const { name: userName, age: userAge } = user;

// With default values
const { name, role = "student" } = user;

// Array destructuring
const colors = ["red", "green", "blue"];
const [first, second, third] = colors;
const [primary, , accent] = colors; // skip second

// In function parameters
function displayUser({ name, age, city = "Unknown" }) {
  console.log(`${name}, ${age}, ${city}`);
}
displayUser(user);

5. Spread and Rest Operators

// Spread: expand an array or object
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// Copy an array
const copy = [...arr1];

// Spread with objects
const defaults = { theme: "dark", lang: "en" };
const userPrefs = { lang: "hi", fontSize: 16 };
const settings = { ...defaults, ...userPrefs };
// { theme: "dark", lang: "hi", fontSize: 16 }

// Rest: collect remaining arguments
function sum(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4, 5); // 15

// Rest in destructuring
const [first, ...rest] = [1, 2, 3, 4, 5];
// first = 1, rest = [2, 3, 4, 5]

6. Promises and Async/Await

Async/await makes asynchronous code look and behave like synchronous code, eliminating callback hell.

// Old way: callback hell
fetch('/api/user', function(err, user) {
  if (err) { handleError(err); return; }
  fetch('/api/posts/' + user.id, function(err, posts) {
    if (err) { handleError(err); return; }
    displayPosts(posts);
  });
});

// Modern: async/await
async function loadUserPosts(userId) {
  try {
    const userRes = await fetch(`/api/user/${userId}`);
    const user = await userRes.json();

    const postsRes = await fetch(`/api/posts/${user.id}`);
    const posts = await postsRes.json();

    return posts;
  } catch (error) {
    console.error('Failed to load:', error);
  }
}

// Parallel requests
async function loadDashboard() {
  const [user, posts, notifications] = await Promise.all([
    fetch('/api/user').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
    fetch('/api/notifications').then(r => r.json())
  ]);
  return { user, posts, notifications };
}

7. Optional Chaining (?.) and Nullish Coalescing (??)

const user = {
  name: "Priya",
  address: {
    city: "Pune"
  }
};

// Old way: verbose null checks
const city = user && user.address && user.address.city;
const zip = user && user.address && user.address.zip || "N/A";

// Optional chaining
const city = user?.address?.city;       // "Pune"
const zip = user?.address?.zip;         // undefined (no error)
const phone = user?.contact?.phone;     // undefined (no error)

// Nullish coalescing: use default only if null/undefined (not 0 or "")
const zip = user?.address?.zip ?? "N/A";  // "N/A"
const count = user?.postCount ?? 0;       // 0

8. ES Modules (import/export)

// math.js - named exports
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }

// utils.js - default export
export default function formatDate(date) {
  return new Intl.DateTimeFormat('en-IN').format(date);
}

// main.js - importing
import formatDate from './utils.js';           // default import
import { add, multiply, PI } from './math.js'; // named imports
import * as math from './math.js';             // import all as namespace

console.log(add(2, 3));       // 5
console.log(math.PI);         // 3.14159
console.log(formatDate(new Date())); // "15/06/2026"

9. Array Methods You Should Know

const students = [
  { name: "Rahul", score: 85, passed: true },
  { name: "Priya", score: 92, passed: true },
  { name: "Amit", score: 45, passed: false },
  { name: "Sara", score: 78, passed: true }
];

// filter: keep items that match condition
const passed = students.filter(s => s.passed);

// map: transform each item
const names = students.map(s => s.name);
const scores = students.map(s => ({ name: s.name, grade: s.score >= 90 ? 'A' : 'B' }));

// find: get first matching item
const topStudent = students.find(s => s.score > 90);

// some / every
const anyFailed = students.some(s => !s.passed);   // true
const allPassed = students.every(s => s.passed);    // false

// reduce: aggregate
const totalScore = students.reduce((sum, s) => sum + s.score, 0);
const avgScore = totalScore / students.length;

// sort (returns new array in modern practice)
const sorted = [...students].sort((a, b) => b.score - a.score);

10. Object Shorthand and Computed Properties

const name = "Rahul";
const age = 21;

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

// Shorthand (when variable name matches key)
const user = { name, age };

// Computed property names
const key = "score";
const data = { [key]: 95 }; // { score: 95 }

// Method shorthand
const calculator = {
  value: 0,
  add(n) { this.value += n; return this; },
  subtract(n) { this.value -= n; return this; },
  result() { return this.value; }
};
calculator.add(10).add(5).subtract(3).result(); // 12

These features are not just syntactic sugar — they make your code more readable, less error-prone, and easier to maintain. Modern JavaScript frameworks like React, Vue, and Angular use all of these features extensively. Mastering them is essential for any serious JavaScript developer.