Skip to main content

Modern ES6+ Features

Why This Matters

ES6 (2015) and the annual releases since have added features that have fundamentally changed how JavaScript is written. Understanding these lets you read modern code fluently and write cleaner, more expressive JavaScript.

Spread Operator (...)

The spread operator expands an iterable (array, string, object) into individual elements.

With Arrays

const a = [1, 2, 3];
const b = [4, 5, 6];

// Combine arrays
const combined = [...a, ...b]; // [1, 2, 3, 4, 5, 6]

// Copy an array (shallow)
const copy = [...a]; // [1, 2, 3]

// Pass array elements as function arguments
Math.max(...a); // 3

With Objects

const user = { name: "Rizwan", age: 25 };
const location = { city: "Lahore", country: "Pakistan" };

// Merge objects
const fullProfile = { ...user, ...location };
// { name: "Rizwan", age: 25, city: "Lahore", country: "Pakistan" }

// Clone with overrides
const updated = { ...user, age: 26 };
// { name: "Rizwan", age: 26 }

Later properties override earlier ones when keys conflict:

const base = { color: "red", size: "M" };
const override = { ...base, color: "blue" }; // color is now "blue"

Rest Parameters (...)

Same syntax, opposite purpose: rest collects multiple values into an array.

// Collect remaining function arguments
function sum(first, ...rest) {
return rest.reduce((total, n) => total + n, first);
}

sum(1, 2, 3, 4); // 10
// Collect remaining object properties
const { name, age, ...others } = { name: "Rizwan", age: 25, city: "Lahore", role: "dev" };
// name = "Rizwan", age = 25, others = { city: "Lahore", role: "dev" }

Optional Chaining (?.)

Safely access nested properties without crashing when an intermediate value is null or undefined.

Without optional chaining (verbose and error-prone):

const city = user && user.address && user.address.city;

With optional chaining:

const city = user?.address?.city; // undefined if any part is nullish

Works with method calls and array access too:

user?.getFullName?.(); // call method only if it exists
data?.items?.[0]?.title; // safe array index access

A common real-world example — reading an API response:

const token = response?.data?.auth?.token;
const firstTag = post?.tags?.[0];

Nullish Coalescing (??)

Return the right-hand value only if the left side is null or undefined (not just falsy).

const name = user.name ?? "Anonymous"; // only falls back if name is null/undefined

Compare with || (OR), which falls back on any falsy value:

const countOr = 0 || "default"; // "default" — probably not what you want!
const countNc = 0 ?? "default"; // 0 — correct, 0 is a valid value

Use ?? when 0, false, or "" are valid values you don't want to replace.

Optional Chaining + Nullish Coalescing Together

A very common pattern:

const displayName = user?.profile?.displayName ?? "Anonymous User";
const port = config?.server?.port ?? 3000;

Map and Set

Set — Collection of Unique Values

const set = new Set([1, 2, 3, 2, 1]);
console.log(set); // Set {1, 2, 3} — duplicates removed

set.add(4);
set.has(3); // true
set.delete(1);
set.size; // 3

// Most common use — remove duplicates from an array
const unique = [...new Set([1, 2, 2, 3, 3, 4])]; // [1, 2, 3, 4]

Map — Key-Value Pairs with Any Key Type

Unlike plain objects, Map keys can be any type (objects, functions, etc.) and insertion order is preserved.

const map = new Map();
map.set("name", "Rizwan");
map.set(42, "the answer");
map.set(true, "boolean key");

map.get("name"); // "Rizwan"
map.has(42); // true
map.delete(true);
map.size; // 2

// Iterate
map.forEach((value, key) => console.log(key, value));
for (const [key, value] of map) { ... }

When to use Map over a plain object:

  • Keys that aren't strings
  • Need to know the size quickly (map.size)
  • Frequent additions and deletions
  • Need ordered iteration

Short-Circuit Evaluation

// && returns the first falsy value, or the last value if all are truthy
const result = isLoggedIn && <Dashboard />; // renders Dashboard or false

// || returns the first truthy value, or the last value
const name = input || "default";

// Common pattern: conditional execution
isAdmin && deleteUser(id); // only runs if isAdmin is true

Tagged Template Literals

You already know template literals:

const greeting = `Hello, ${name}!`;

For multi-line strings:

const query = `
SELECT *
FROM users
WHERE id = ${userId}
`;
Don't interpolate user input into SQL queries

The example above is for illustration. In production, always use parameterized queries — never interpolate user input into SQL strings directly, as it leads to SQL injection.

Object.entries, Object.keys, Object.values

Iterate over objects like arrays:

const scores = { alice: 95, bob: 87, carol: 92 };

Object.keys(scores); // ["alice", "bob", "carol"]
Object.values(scores); // [95, 87, 92]
Object.entries(scores); // [["alice", 95], ["bob", 87], ["carol", 92]]

// Find the highest scorer
const [topName, topScore] = Object.entries(scores)
.sort(([, a], [, b]) => b - a)[0];
// topName = "alice", topScore = 95

Transform an object:

const doubled = Object.fromEntries(
Object.entries(scores).map(([key, val]) => [key, val * 2])
);
// { alice: 190, bob: 174, carol: 184 }

Quick Reference

FeatureSyntaxUse for
Spread (array)[...arr]Copy/merge arrays, pass as args
Spread (object){...obj}Copy/merge objects
Rest(...args)Collect remaining args/properties
Optional chaininga?.b?.cSafe nested property access
Nullish coalescinga ?? bDefault only for null/undefined
Setnew Set(arr)Unique values, deduplication
Mapnew Map()Key-value with non-string keys