Modern JavaScript Patterns
Comprehensive guide for mastering modern JavaScript (ES6+) features, functional programming patterns, and best practices for writing clean, maintainable, and performant code.
When to Use This Skill
- Refactoring legacy JavaScript to modern syntax
- Implementing functional programming patterns
- Optimizing JavaScript performance
- Writing maintainable and readable code
- Working with asynchronous operations
- Building modern web applications
- Migrating from callbacks to Promises/async-await
- Implementing data transformation pipelines
ES6+ Core Features
1. Arrow Functions
Syntax and Use Cases:
javascript// Traditional function function add(a, b) { return a + b; } // Arrow function const add = (a, b) => a + b; // Single parameter (parentheses optional) const double = x => x * 2; // No parameters const getRandom = () => Math.random(); // Multiple statements (need curly braces) const processUser = user => { const normalized = user.name.toLowerCase(); return { ...user, name: normalized }; }; // Returning objects (wrap in parentheses) const createUser = (name, age) => ({ name, age });
Lexical 'this' Binding:
javascriptclass Counter { constructor() { this.count = 0; } // Arrow function preserves 'this' context increment = () => { this.count++; }; // Traditional function loses 'this' in callbacks incrementTraditional() { setTimeout(function() { this.count++; // 'this' is undefined }, 1000); } // Arrow function maintains 'this' incrementArrow() { setTimeout(() => { this.count++; // 'this' refers to Counter instance }, 1000); } }
2. Destructuring
Object Destructuring:
javascriptconst user = { id: 1, name: 'John Doe', email: 'john@example.com', address: { city: 'New York', country: 'USA' } }; // Basic destructuring const { name, email } = user; // Rename variables const { name: userName, email: userEmail } = user; // Default values const { age = 25 } = user; // Nested destructuring const { address: { city, country } } = user; // Rest operator const { id, ...userWithoutId } = user; // Function parameters function greet({ name, age = 18 }) { console.log(`Hello ${name}, you are ${age}`); } greet(user);
Array Destructuring:
javascriptconst numbers = [1, 2, 3, 4, 5]; // Basic destructuring const [first, second] = numbers; // Skip elements const [, , third] = numbers; // Rest operator const [head, ...tail] = numbers; // Swapping variables let a = 1, b = 2; [a, b] = [b, a]; // Function return values function getCoordinates() { return [10, 20]; } const [x, y] = getCoordinates(); // Default values const [one, two, three = 0] = [1, 2];
3. Spread and Rest Operators
Spread Operator:
javascript// Array spreading const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const combined = [...arr1, ...arr2]; // Object spreading const defaults = { theme: 'dark', lang: 'en' }; const userPrefs = { theme: 'light' }; const settings = { ...defaults, ...userPrefs }; // Function arguments const numbers = [1, 2, 3]; Math.max(...numbers); // Copying arrays/objects (shallow copy) const copy = [...arr1]; const objCopy = { ...user }; // Adding items immutably const newArr = [...arr1, 4, 5]; const newObj = { ...user, age: 30 };
Rest Parameters:
javascript// Collect function arguments function sum(...numbers) { return numbers.reduce((total, num) => total + num, 0); } sum(1, 2, 3, 4, 5); // With regular parameters function greet(greeting, ...names) { return `${greeting} ${names.join(', ')}`; } greet('Hello', 'John', 'Jane', 'Bob'); // Object rest const { id, ...userData } = user; // Array rest const [first, ...rest] = [1, 2, 3, 4, 5];
4. Template Literals
javascript// Basic usage const name = 'John'; const greeting = `Hello, ${name}!`; // Multi-line strings const html = ` <div> <h1>${title}</h1> <p>${content}</p> </div> `; // Expression evaluation const price = 19.99; const total = `Total: $${(price * 1.2).toFixed(2)}`; // Tagged template literals function highlight(strings, ...values) { return strings.reduce((result, str, i) => { const value = values[i] || ''; return result + str + `<mark>${value}</mark>`; }, ''); } const name = 'John'; const age = 30; const html = highlight`Name: ${name}, Age: ${age}`; // Output: "Name: <mark>John</mark>, Age: <mark>30</mark>"
5. Enhanced Object Literals
javascriptconst name = 'John'; const age = 30; // Shorthand property names const user = { name, age }; // Shorthand method names const calculator = { add(a, b) { return a + b; }, subtract(a, b) { return a - b; } }; // Computed property names const field = 'email'; const user = { name: 'John', [field]: 'john@example.com', [`get${field.charAt(0).toUpperCase()}${field.slice(1)}`]() { return this[field]; } }; // Dynamic property creation const createUser = (name, ...props) => { return props.reduce((user, [key, value]) => ({ ...user, [key]: value }), { name }); }; const user = createUser('John', ['age', 30], ['email', 'john@example.com']);
Asynchronous Patterns
1. Promises
Creating and Using Promises:
javascript// Creating a promise const fetchUser = (id) => { return new Promise((resolve, reject) => { setTimeout(() => { if (id > 0) { resolve({ id, name: 'John' }); } else { reject(new Error('Invalid ID')); } }, 1000); }); }; // Using promises fetchUser(1) .then(user => console.log(user)) .catch(error => console.error(error)) .finally(() => console.log('Done')); // Chaining promises fetchUser(1) .then(user => fetchUserPosts(user.id)) .then(posts => processPosts(posts)) .then(result => console.log(result)) .catch(error => console.error(error));
Promise Combinators:
javascript// Promise.all - Wait for all promises const promises = [ fetchUser(1), fetchUser(2), fetchUser(3) ]; Promise.all(promises) .then(users => console.log(users)) .catch(error => console.error('At least one failed:', error)); // Promise.allSettled - Wait for all, regardless of outcome Promise.allSettled(promises) .then(results => { results.forEach(result => { if (result.status === 'fulfilled') { console.log('Success:', result.value); } else { console.log('Error:', result.reason); } }); }); // Promise.race - First to complete Promise.race(promises) .then(winner => console.log('First:', winner)) .catch(error => console.error(error)); // Promise.any - First to succeed Promise.any(promises) .then(first => console.log('First success:', first)) .catch(error => console.error('All failed:', error));
2. Async/Await
Basic Usage:
javascript// Async function always returns a Promise async function fetchUser(id) { const response = await fetch(`/api/users/${id}`); const user = await response.json(); return user; } // Error handling with try/catch async function getUserData(id) { try { const user = await fetchUser(id); const posts = await fetchUserPosts(user.id); return { user, posts }; } catch (error) { console.error('Error fetching data:', error); throw error; } } // Sequential vs Parallel execution async function sequential() { const user1 = await fetchUser(1); // Wait const user2 = await fetchUser(2); // Then wait return [user1, user2]; } async function parallel() { const [user1, user2] = await Promise.all([ fetchUser(1), fetchUser(2) ]); return [user1, user2]; }
Advanced Patterns:
javascript// Async IIFE (async () => { const result = await someAsyncOperation(); console.log(result); })(); // Async iteration async function processUsers(userIds) { for (const id of userIds) { const user = await fetchUser(id); await processUser(user); } } // Top-level await (ES2022) const config = await fetch('/config.json').then(r => r.json()); // Retry logic async function fetchWithRetry(url, retries = 3) { for (let i = 0; i < retries; i++) { try { return await fetch(url); } catch (error) { if (i === retries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } } // Timeout wrapper async function withTimeout(promise, ms) { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms) ); return Promise.race([promise, timeout]); }
Functional Programming Patterns
1. Array Methods
Map, Filter, Reduce:
javascriptconst users = [ { id: 1, name: 'John', age: 30, active: true }, { id: 2, name: 'Jane', age: 25, active: false }, { id: 3, name: 'Bob', age: 35, active: true } ]; // Map - Transform array const names = users.map(user => user.name); const upperNames = users.map(user => user.name.toUpperCase()); // Filter - Select elements const activeUsers = users.filter(user => user.active); const adults = users.filter(user => user.age >= 18); // Reduce - Aggregate data const totalAge = users.reduce((sum, user) => sum + user.age, 0); const avgAge = totalAge / users.length; // Group by property const byActive = users.reduce((groups, user) => { const key = user.active ? 'active' : 'inactive'; return { ...groups, [key]: [...(groups[key] || []), user] }; }, {}); // Chaining methods const result = users .filter(user => user.active) .map(user => user.name) .sort() .join(', ');
Advanced Array Methods:
javascript// Find - First matching element const user = users.find(u => u.id === 2); // FindIndex - Index of first match const index = users.findIndex(u => u.name === 'Jane'); // Some - At least one matches const hasActive = users.some(u => u.active); // Every - All match const allAdults = users.every(u => u.age >= 18); // FlatMap - Map and flatten const userTags = [ { name: 'John', tags: ['admin', 'user'] }, { name: 'Jane', tags: ['user'] } ]; const allTags = userTags.flatMap(u => u.tags); // From - Create array from iterable const str = 'hello'; const chars = Array.from(str); const numbers = Array.from({ length: 5 }, (_, i) => i + 1); // Of - Create array from arguments const arr = Array.of(1, 2, 3);
2. Higher-Order Functions
Functions as Arguments:
javascript// Custom forEach function forEach(array, callback) { for (let i = 0; i < array.length; i++) { callback(array[i], i, array); } } // Custom map function map(array, transform) { const result = []; for (const item of array) { result.push(transform(item)); } return result; } // Custom filter function filter(array, predicate) { const result = []; for (const item of array) { if (predicate(item)) { result.push(item); } } return result; }
Functions Returning Functions:
javascript// Currying const multiply = a => b => a * b; const double = multiply(2); const triple = multiply(3); console.log(double(5)); // 10 console.log(triple(5)); // 15 // Partial application function partial(fn, ...args) { return (...moreArgs) => fn(...args, ...moreArgs); } const add = (a, b, c) => a + b + c; const add5 = partial(add, 5); console.log(add5(3, 2)); // 10 // Memoization function memoize(fn) { const cache = new Map(); return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn(...args); cache.set(key, result); return result; }; } const fibonacci = memoize((n) => { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); });
3. Composition and Piping
javascript// Function composition const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x); const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x); // Example usage const addOne = x => x + 1; const double = x => x * 2; const square = x => x * x; const composed = compose(square, double, addOne); console.log(composed(3)); // ((3 + 1) * 2)^2 = 64 const piped = pipe(addOne, double, square); console.log(piped(3)); // ((3 + 1) * 2)^2 = 64 // Practical example const processUser = pipe( user => ({ ...user, name: user.name.trim() }), user => ({ ...user, email: user.email.toLowerCase() }), user => ({ ...user, age: parseInt(user.age) }) ); const user = processUser({ name: ' John ', email: 'JOHN@EXAMPLE.COM', age: '30' });
4. Pure Functions and Immutability
javascript// Impure function (modifies input) function addItemImpure(cart, item) { cart.items.push(item); cart.total += item.price; return cart; } // Pure function (no side effects) function addItemPure(cart, item) { return { ...cart, items: [...cart.items, item], total: cart.total + item.price }; } // Immutable array operations const numbers = [1, 2, 3, 4, 5]; // Add to array const withSix = [...numbers, 6]; // Remove from array const withoutThree = numbers.filter(n => n !== 3); // Update array element const doubled = numbers.map(n => n === 3 ? n * 2 : n); // Immutable object operations const user = { name: 'John', age: 30 }; // Update property const olderUser = { ...user, age: 31 }; // Add property const withEmail = { ...user, email: 'john@example.com' }; // Remove property const { age, ...withoutAge } = user; // Deep cloning (simple approach) const deepClone = obj => JSON.parse(JSON.stringify(obj)); // Better deep cloning const structuredClone = obj => globalThis.structuredClone(obj);
Modern Class Features
javascript// Class syntax class User { // Private fields #password; // Public fields id; name; // Static field static count = 0; constructor(id, name, password) { this.id = id; this.name = name; this.#password = password; User.count++; } // Public method greet() { return `Hello, ${this.name}`; } // Private method #hashPassword(password) { return `hashed_${password}`; } // Getter get displayName() { return this.name.toUpperCase(); } // Setter set password(newPassword) { this.#password = this.#hashPassword(newPassword); } // Static method static create(id, name, password) { return new User(id, name, password); } } // Inheritance class Admin extends User { constructor(id, name, password, role) { super(id, name, password); this.role = role; } greet() { return `${super.greet()}, I'm an admin`; } }
Modules (ES6)
javascript// Exporting // math.js export const PI = 3.14159; export function add(a, b) { return a + b; } export class Calculator { // ... } // Default export export default function multiply(a, b) { return a * b; } // Importing // app.js import multiply, { PI, add, Calculator } from './math.js'; // Rename imports import { add as sum } from './math.js'; // Import all import * as Math from './math.js'; // Dynamic imports const module = await import('./math.js'); const { add } = await import('./math.js'); // Conditional loading if (condition) { const module = await import('./feature.js'); module.init(); }
Iterators and Generators
javascript// Custom iterator const range = { from: 1, to: 5, [Symbol.iterator]() { return { current: this.from, last: this.to, next() { if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; for (const num of range) { console.log(num); // 1, 2, 3, 4, 5 } // Generator function function* rangeGenerator(from, to) { for (let i = from; i <= to; i++) { yield i; } } for (const num of rangeGenerator(1, 5)) { console.log(num); } // Infinite generator function* fibonacci() { let [prev, curr] = [0, 1]; while (true) { yield curr; [prev, curr] = [curr, prev + curr]; } } // Async generator async function* fetchPages(url) { let page = 1; while (true) { const response = await fetch(`${url}?page=${page}`); const data = await response.json(); if (data.length === 0) break; yield data; page++; } } for await (const page of fetchPages('/api/users')) { console.log(page); }
Modern Operators
javascript// Optional chaining const user = { name: 'John', address: { city: 'NYC' } }; const city = user?.address?.city; const zipCode = user?.address?.zipCode; // undefined // Function call const result = obj.method?.(); // Array access const first = arr?.[0]; // Nullish coalescing const value = null ?? 'default'; // 'default' const value = undefined ?? 'default'; // 'default' const value = 0 ?? 'default'; // 0 (not 'default') const value = '' ?? 'default'; // '' (not 'default') // Logical assignment let a = null; a ??= 'default'; // a = 'default' let b = 5; b ??= 10; // b = 5 (unchanged) let obj = { count: 0 }; obj.count ||= 1; // obj.count = 1 obj.count &&= 2; // obj.count = 2
Performance Optimization
javascript// Debounce function debounce(fn, delay) { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn(...args), delay); }; } const searchDebounced = debounce(search, 300); // Throttle function throttle(fn, limit) { let inThrottle; return (...args) => { if (!inThrottle) { fn(...args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } const scrollThrottled = throttle(handleScroll, 100); // Lazy evaluation function* lazyMap(iterable, transform) { for (const item of iterable) { yield transform(item); } } // Use only what you need const numbers = [1, 2, 3, 4, 5]; const doubled = lazyMap(numbers, x => x * 2); const first = doubled.next().value; // Only computes first value
Best Practices
- Use const by default: Only use let when reassignment is needed
- Prefer arrow functions: Especially for callbacks
- Use template literals: Instead of string concatenation
- Destructure objects and arrays: For cleaner code
- Use async/await: Instead of Promise chains
- Avoid mutating data: Use spread operator and array methods
- Use optional chaining: Prevent "Cannot read property of undefined"
- Use nullish coalescing: For default values
- Prefer array methods: Over traditional loops
- Use modules: For better code organization
- Write pure functions: Easier to test and reason about
- Use meaningful variable names: Self-documenting code
- Keep functions small: Single responsibility principle
- Handle errors properly: Use try/catch with async/await
- Use strict mode:
'use strict'for better error catching
Common Pitfalls
- this binding confusion: Use arrow functions or bind()
- Async/await without error handling: Always use try/catch
- Promise creation unnecessary: Don't wrap already async functions
- Mutation of objects: Use spread operator or Object.assign()
- Forgetting await: Async functions return promises
- Blocking event loop: Avoid synchronous operations
- Memory leaks: Clean up event listeners and timers
- Not handling promise rejections: Use catch() or try/catch
Resources
- MDN Web Docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript
- JavaScript.info: https://javascript.info/
- You Don't Know JS: https://github.com/getify/You-Dont-Know-JS
- Eloquent JavaScript: https://eloquentjavascript.net/
- ES6 Features: http://es6-features.org/

