JavaScript runs everywhere β browsers, servers, mobile apps, even embedded devices. Whether youβre building a React frontend or a Node.js API, youβre writing JavaScript (or TypeScript, which compiles to it). This guide covers modern JavaScript (ES2015+) with practical patterns youβll use daily.
For quick reference, see the JavaScript array methods cheat sheet. Preparing for interviews? Check our JavaScript interview questions.
Variables and Scope
Always use const by default. Use let when you need to reassign. Never use var.
const name = 'Alice'; // can't reassign
let count = 0; // can reassign
count = 1; // β
const user = { name: 'Alice' };
user.name = 'Bob'; // β
β const prevents reassignment, not mutation
Block Scope
let and const are block-scoped β they only exist inside their {}:
if (true) {
const x = 10;
let y = 20;
}
// x and y don't exist here
Functions
// Function declaration (hoisted)
function greet(name) {
return `Hello, ${name}`;
}
// Arrow function (not hoisted, lexical `this`)
const greet = (name) => `Hello, ${name}`;
// Default parameters
const greet = (name = 'World') => `Hello, ${name}`;
// Rest parameters
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
Arrow functions donβt have their own this β they inherit it from the surrounding scope. This makes them ideal for callbacks but unsuitable for object methods that need this.
Destructuring
Pull values out of objects and arrays:
// Object destructuring
const { name, age, role = 'user' } = user;
// Array destructuring
const [first, second, ...rest] = items;
// Function parameters
function createUser({ name, email, role = 'user' }) {
return { name, email, role };
}
// Renaming
const { name: userName, age: userAge } = user;
Spread and Rest
// Spread β expand arrays/objects
const merged = { ...defaults, ...overrides };
const all = [...arr1, ...arr2];
// Shallow clone
const copy = { ...original };
const arrCopy = [...original];
// Rest β collect remaining items
const { id, ...rest } = user; // rest has everything except id
Template Literals
const message = `Hello, ${name}! You have ${count} items.`;
// Multi-line strings
const html = `
<div class="card">
<h2>${title}</h2>
<p>${description}</p>
</div>
`;
// Tagged templates (used by libraries like styled-components, GraphQL)
const query = gql`
query GetUser($id: ID!) {
user(id: $id) { name email }
}
`;
Arrays
JavaScript arrays are incredibly powerful. See the full array methods cheat sheet for all methods.
const nums = [1, 2, 3, 4, 5];
// Transform
nums.map(n => n * 2); // [2, 4, 6, 8, 10]
nums.filter(n => n > 3); // [4, 5]
nums.reduce((sum, n) => sum + n, 0); // 15
// Search
nums.find(n => n > 3); // 4
nums.findIndex(n => n > 3); // 3
nums.includes(3); // true
nums.some(n => n > 4); // true
nums.every(n => n > 0); // true
// Modify
nums.flat(); // flatten nested arrays
nums.flatMap(n => [n, n * 2]); // map + flatten
// Modern additions
Object.groupBy(people, p => p.role); // group by key (ES2024)
Promises and Async/Await
Async/await is the modern way to handle asynchronous code:
// async/await (preferred)
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
}
// Parallel requests
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
]);
// Promise.allSettled β don't fail if one rejects
const results = await Promise.allSettled([
fetch('/api/a'),
fetch('/api/b'),
fetch('/api/c'),
]);
// results: [{ status: 'fulfilled', value: ... }, { status: 'rejected', reason: ... }]
If youβre getting unhandled promise rejections, see our unhandled rejection fix.
ES Modules
// Named exports
export const API_URL = 'https://api.example.com';
export function fetchData() { /* ... */ }
// Default export
export default class UserService { /* ... */ }
// Importing
import UserService, { API_URL, fetchData } from './user-service.js';
// Dynamic import (code splitting)
const { Chart } = await import('./chart.js');
If youβre getting βCannot use import statement outside a module,β see our import outside module fix. For βrequire is not defined,β see the require not defined fix.
Classes
class User {
#password; // private field
constructor(name, email) {
this.name = name;
this.email = email;
}
static fromJSON(json) {
return new User(json.name, json.email);
}
get displayName() {
return this.name || this.email;
}
toString() {
return `${this.name} <${this.email}>`;
}
}
class Admin extends User {
constructor(name, email, permissions) {
super(name, email);
this.permissions = permissions;
}
}
Optional Chaining and Nullish Coalescing
Two of the most useful modern operators:
// Optional chaining β short-circuit on null/undefined
const city = user?.address?.city;
const first = arr?.[0];
const result = obj?.method?.();
// Nullish coalescing β default only for null/undefined (not 0 or '')
const port = config.port ?? 3000;
const name = user.name ?? 'Anonymous';
// Combined
const theme = user?.preferences?.theme ?? 'light';
Error Handling
// try/catch/finally
try {
const data = JSON.parse(input);
} catch (error) {
if (error instanceof SyntaxError) {
console.error('Invalid JSON:', error.message);
} else {
throw error; // re-throw unexpected errors
}
} finally {
cleanup(); // always runs
}
// Custom errors
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
Closures
A closure is a function that remembers variables from its outer scope:
function createCounter(initial = 0) {
let count = initial;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
};
}
const counter = createCounter(10);
counter.increment(); // 11
counter.increment(); // 12
counter.getCount(); // 12
Closures are everywhere in JavaScript β event handlers, callbacks, React hooks, and module patterns all rely on them.
Iterators and Generators
// Generator function
function* range(start, end) {
for (let i = start; i < end; i++) {
yield i;
}
}
for (const n of range(0, 5)) {
console.log(n); // 0, 1, 2, 3, 4
}
// Async generator
async function* fetchPages(url) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (data.length === 0) return;
yield data;
page++;
}
}
Proxy and Reflect
For metaprogramming and reactive systems:
const handler = {
get(target, prop) {
console.log(`Accessing ${prop}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
console.log(`Setting ${prop} = ${value}`);
return Reflect.set(target, prop, value);
},
};
const user = new Proxy({ name: 'Alice' }, handler);
user.name; // logs: Accessing name
user.age = 30; // logs: Setting age = 30
Vue.js 3βs reactivity system is built on Proxy.
WeakMap and WeakSet
For caching and metadata without preventing garbage collection:
const cache = new WeakMap();
function expensiveComputation(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = /* heavy work */ obj.data.length * 42;
cache.set(obj, result);
return result;
}
// When obj is garbage collected, the cache entry is automatically removed
Structuring Modern JavaScript Projects
// Use barrel exports for clean imports
// utils/index.js
export { formatDate } from './date.js';
export { slugify } from './string.js';
export { debounce, throttle } from './timing.js';
// Consumer
import { formatDate, slugify, debounce } from './utils/index.js';
Patterns that scale:
- One function/class per file
- Group by feature, not by type (
user/api.js,user/model.jsinstead ofapi/user.js,models/user.js) - Use ES modules everywhere β CommonJS (
require) is legacy - Prefer named exports over default exports for better refactoring support
Performance Tips
// Use Map for frequent lookups (faster than object for dynamic keys)
const userMap = new Map();
userMap.set(userId, userData);
userMap.get(userId); // O(1) lookup
// Use Set for unique values
const uniqueTags = new Set(['js', 'react', 'js']); // Set(2) {'js', 'react'}
// Avoid creating objects in hot loops
// β Creates a new regex every iteration
for (const line of lines) {
if (line.match(/error/i)) count++;
}
// β
Create once, reuse
const errorPattern = /error/i;
for (const line of lines) {
if (errorPattern.test(line)) count++;
}
Common Runtime Errors
JavaScript errors can be cryptic. Here are detailed fixes for the most common ones:
- TypeError: Cannot read properties of undefined β accessing a property on
undefined - TypeError: X is not a function β calling something that isnβt callable
- TypeError: forEach is not a function β iterating a non-array
- TypeError: Assignment to constant variable β reassigning a
const - TypeError: Cannot set property β setting a property on null/undefined
- ReferenceError: X is not defined β using an undeclared variable
- SyntaxError: Unexpected token β parsing error
- SyntaxError: Missing semicolon β syntax issue
- RangeError: Maximum call stack β infinite recursion
- Unhandled promise rejection β uncaught async error
Next Steps
- Master array methods β youβll use them constantly
- Add type safety with TypeScript
- Build UIs with React or full apps with Next.js
- Clean up your code with our JavaScript minifier & beautifier
- Manage dependencies with npm