Why JavaScript matters
JavaScript is the language that makes web pages interactive. It also powers servers, tooling, mobile apps, desktop applications, and many modern developer workflows.
Build real programming confidence with JavaScript fundamentals, browser APIs, DOM manipulation, asynchronous workflows, debugging skills, and modern ES6+ syntax used in today's web applications.
JavaScript is the language that makes web pages interactive. It also powers servers, tooling, mobile apps, desktop applications, and many modern developer workflows.
This page now follows the same richer course design language as the improved HTML tutorial, with a stronger top section and better full-page reading experience.
The updated content clearly explains the course purpose, learning outcomes, and topic coverage, making the page more useful for readers and easier for search engines to understand.
Set up your environment, run your first scripts, and build confidence in both browser and Node.js contexts.
JavaScript runs everywhere: directly in the browser for UI work, and on the server with Node.js for tooling and APIs. This section walks through the essential setup, execution paths, and debugging habits that underpin productive learning.
Create a minimal HTML document so you always know where your JavaScript executes. Keep structure, styles, and scripts organized from day one.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS Playground</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Welcome to JavaScript</h1>
<p id="status">Loading...</p>
<script>
// Inline scripts run immediately when the parser reaches them
document.getElementById('status').textContent = 'Inline script executed';
</script>
<script src="app.js"></script>
</body>
</html>
Inline scripts are quick for experiments, while external files keep production code organized and cacheable. Always prefer defer for external files so HTML can finish parsing first.
<script>
// Inline snippet for a quick message
console.log('Inline code runs immediately');
</script>
<script src="/js/main.js" defer></script>
// main.js
// The "defer" attribute ensures the DOM is parsed before this executes
document.addEventListener('DOMContentLoaded', () => {
const banner = document.querySelector('h1');
banner.textContent = 'JavaScript is connected!';
});
The browser console is your first debugging companion. Use it to log, group, and trace the flow of your code.
console.log('Simple message');
console.info('Informational', { env: 'dev' });
console.warn('Warning: slow network');
console.error('Error: missing token');
console.group('Auth Flow');
console.log('Fetching user');
console.log('Validating session');
console.groupEnd();
Build tiny, interactive snippets that respond to user actions. Use DOM queries and event listeners to change content without reloading.
<button id="action">Click me</button>
<p id="message">Waiting...</p>
<script>
const button = document.getElementById('action');
const message = document.getElementById('message');
button.addEventListener('click', () => {
message.textContent = 'Button clicked at ' + new Date().toLocaleTimeString();
});
</script>
document.addEventListener('keydown', event => {
console.log(`Key pressed: ${event.key}`);
});
Open DevTools (F12 or Ctrl+Shift+I) to inspect elements, view network calls, and profile performance. Use the Sources panel to explore files and set breakpoints.
// Paste into the console to experiment without editing files
const el = document.querySelector('#status');
el.style.color = '#0a8754';
el.textContent = 'Live edit success!';
// Quick timer for code you want to measure
console.time('loop');
for (let i = 0; i < 100000; i++) {
Math.sqrt(i);
}
console.timeEnd('loop');
Set breakpoints in the Sources tab to pause execution. Step through code to inspect variables and watch expressions.
function greet(user) {
const message = `Welcome, ${user}`;
debugger; // Execution pauses here when DevTools is open
console.log(message);
}
greet('Amina');
Use the Fetch API to retrieve data asynchronously. Combine async/await with error handling to keep UX resilient.
async function loadQuote() {
const status = document.getElementById('status');
status.textContent = 'Loading...';
try {
const response = await fetch('https://api.quotable.io/random');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
status.textContent = data.content;
} catch (error) {
console.error(error);
status.textContent = 'Could not load quote. Please retry.';
}
}
document.addEventListener('DOMContentLoaded', loadQuote);
Node.js lets you run JavaScript outside the browser. Use it for build tools, scripts, or quick experiments without HTML.
# Verify Node and npm
node -v
npm -v
# Run a simple script
node hello.js
// hello.js
console.log('Running in Node.js');
// Access filesystem with fs (built-in module)
const fs = require('fs');
fs.writeFileSync('log.txt', 'Hello from Node at ' + new Date().toISOString());
Use try/catch around risky operations. Provide user-friendly feedback while logging detailed errors for yourself.
function parseSettings(jsonString) {
try {
const settings = JSON.parse(jsonString);
return settings;
} catch (error) {
console.error('Could not parse settings', error);
return { theme: 'light', retries: 0 };
}
}
const config = parseSettings('{ "theme": "dark" }');
console.log(config.theme);
console.time.defer attribute to keep scripts predictable.Continue with variables and control flow in the next sections to write more expressive programs, then explore DOM manipulation patterns to build interactive interfaces.
Discover the world's most popular programming language for the web
JavaScript is a high-level, interpreted programming language that adds interactivity and dynamic behavior to websites. It's the third pillar of web development alongside HTML and CSS, running in every modern web browser and increasingly on servers via Node.js.
JavaScript is a versatile, lightweight, prototype-based language that enables developers to create dynamic, interactive web experiences.
<!DOCTYPE html>
<html>
<head>
<title>JavaScript Demo</title>
</head>
<body>
<h1 id="greeting">Welcome!</h1>
<button id="changeBtn">Click Me</button>
<script>
// Get element references
const greeting = document.getElementById('greeting');
const button = document.getElementById('changeBtn');
// Add click event listener
button.addEventListener('click', () => {
greeting.textContent = 'Hello, JavaScript!';
greeting.style.color = '#3b82f6';
});
</script>
</body>
</html>
Understanding JavaScript's fundamental traits helps you write better code.
// 1. Dynamic Typing
let value = 42; // Number
value = 'Hello'; // Now a String
value = true; // Now a Boolean
// 2. First-Class Functions
const greet = function(name) {
return `Hello, ${name}!`;
};
const sayHello = greet; // Functions are values
console.log(sayHello('World')); // "Hello, World!"
// 3. Prototype-Based Inheritance
const animal = {
eat() { console.log('Eating...'); }
};
const dog = Object.create(animal);
dog.bark = function() { console.log('Woof!'); };
dog.eat(); // Inherited from animal
dog.bark(); // Own method
// 4. Event-Driven & Asynchronous
setTimeout(() => {
console.log('Delayed execution');
}, 1000);
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data));
// 5. Single-Threaded with Event Loop
console.log('First');
setTimeout(() => console.log('Second'), 0);
console.log('Third');
// Output: First, Third, Second
JavaScript has evolved from a browser-only language to a universal runtime.
// 1. Browser (Client-Side)
// - DOM manipulation
document.querySelector('.btn').addEventListener('click', handleClick);
// - Browser APIs
localStorage.setItem('theme', 'dark');
navigator.geolocation.getCurrentPosition(success, error);
// - Fetch API
fetch('/api/users').then(res => res.json());
// 2. Node.js (Server-Side)
// - File system access
const fs = require('fs');
fs.readFileSync('data.txt', 'utf8');
// - HTTP servers
const http = require('http');
http.createServer((req, res) => {
res.end('Hello from Node.js!');
}).listen(3000);
// 3. Mobile Apps
// - React Native
import { View, Text } from 'react-native';
// - Ionic
// - NativeScript
// 4. Desktop Apps
// - Electron (VS Code, Slack, Discord)
const { app, BrowserWindow } = require('electron');
// 5. IoT & Embedded
// - Johnny-Five (Arduino)
// - Tessel
// - Raspberry Pi
// 6. Game Development
// - Phaser
// - Three.js (3D graphics)
See how JavaScript compares to other popular programming languages.
/* JavaScript Strengths:
* ✓ Runs everywhere (browser, server, mobile, desktop)
* ✓ Huge ecosystem (npm - 2M+ packages)
* ✓ Easy to start, no compilation needed
* ✓ Asynchronous by nature
* ✓ JSON is native
* ✓ Functional programming support
* ✓ Active community and constant evolution
*/
// JavaScript: Event-driven & asynchronous
fetch('/api/data')
.then(res => res.json())
.then(data => updateUI(data));
// Python: Synchronous by default (unless async/await)
// import requests
// response = requests.get('/api/data')
// data = response.json()
// Java: Verbose, strongly typed
// HttpClient client = HttpClient.newHttpClient();
// HttpRequest request = HttpRequest.newBuilder()
// .uri(URI.create("/api/data"))
// .build();
/* JavaScript Trade-offs:
* ⚠ Weak typing can cause runtime errors
* ⚠ Callback hell (mitigated by Promises/async-await)
* ⚠ Browser inconsistencies (less of an issue now)
* ⚠ No built-in type safety (TypeScript solves this)
*/
JavaScript is standardized as ECMAScript, with yearly updates bringing new features.
// ES5 (2009) - Baseline modern JS
var array = [1, 2, 3];
var doubled = array.map(function(n) { return n * 2; });
// ES6/ES2015 - Major update
const array = [1, 2, 3];
const doubled = array.map(n => n * 2);
let name = 'Alice';
const greeting = `Hello, ${name}!`; // Template literals
const [first, ...rest] = array; // Destructuring
const person = { name, age: 30 }; // Shorthand properties
class Person {
constructor(name) { this.name = name; }
}
// ES2016 (ES7)
const power = 2 ** 3; // Exponentiation: 8
[1, 2, 3].includes(2); // true
// ES2017 (ES8)
async function fetchData() {
const response = await fetch('/api');
return await response.json();
}
Object.values({ a: 1, b: 2 }); // [1, 2]
Object.entries({ a: 1, b: 2 }); // [['a', 1], ['b', 2]]
// ES2018
const { x, ...others } = { x: 1, y: 2, z: 3 }; // Rest in objects
// ES2019
[1, [2, [3]]].flat(2); // [1, 2, 3]
array.flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
// ES2020
const value = null ?? 'default'; // Nullish coalescing
user?.address?.street; // Optional chaining
Promise.allSettled([p1, p2]); // All promises settle
// ES2021
const str = 'Hello_World';
str.replaceAll('_', ' '); // "Hello World"
// ES2022
class Counter {
#count = 0; // Private field
increment() { this.#count++; }
}
await import('./module.js'); // Top-level await
// ES2023
const arr = [1, 2, 3];
arr.toSorted(); // Non-mutating sort
arr.toReversed(); // Non-mutating reverse
From simple scripts to complex applications, JavaScript powers diverse projects.
// 1. Web Applications
// - Single Page Apps (React, Vue, Angular)
// - Progressive Web Apps
// - E-commerce platforms
// - Social networks
// 2. Backend Development
// - REST APIs (Express.js)
// - GraphQL servers (Apollo)
// - Real-time apps (Socket.io)
// - Microservices
// 3. Mobile Development
// - React Native (iOS/Android)
// - Ionic (hybrid apps)
// - NativeScript
// 4. Desktop Applications
// - VS Code (Electron)
// - Slack (Electron)
// - Discord (Electron)
// 5. Game Development
// - Browser games (Phaser, Three.js)
// - 2D/3D graphics
// - WebGL applications
// 6. Machine Learning
// - TensorFlow.js
// - Brain.js
// - ML5.js
// 7. IoT & Robotics
// - Arduino (Johnny-Five)
// - Raspberry Pi
// - Drones
// 8. Automation & Tooling
// - Build tools (Webpack, Vite)
// - Task runners (Gulp)
// - Testing frameworks (Jest, Cypress)
// - Code generators
Continue with JavaScript History to learn about the language's evolution, or jump to Getting Started to write your first JavaScript code.
From a 10-day prototype to the world's most popular programming language
JavaScript was created in just 10 days in May 1995 by Brendan Eich at Netscape. What started as a simple scripting language has evolved into a powerful, ubiquitous platform powering billions of devices worldwide.
JavaScript's creation story is one of the most remarkable in computing history.
/* May 1995 - Brendan Eich creates JavaScript in 10 days
* Original names: Mocha → LiveScript → JavaScript
*
* Goals:
* - Make the web dynamic and interactive
* - Easy for non-programmers (like Java applets)
* - Complement Java (marketing decision)
* - Scheme-like language with C-like syntax
*/
// Early JavaScript (1995)
// Simple form validation
function validateForm() {
var name = document.forms[0].name.value;
if (name == "") {
alert("Please enter your name");
return false;
}
return true;
}
// December 1995: Netscape Navigator 2.0 ships with JavaScript
// Microsoft responds with JScript in Internet Explorer 3.0 (1996)
The first browser war led to fragmentation and the need for standards.
/* Browser Wars Era:
* 1996: Microsoft releases JScript (IE 3.0)
* 1996: Netscape submits JavaScript to ECMA for standardization
* 1997: ECMAScript 1 (ES1) - First standard
* 1998: ECMAScript 2 (ES2) - Editorial changes
* 1999: ECMAScript 3 (ES3) - Regular expressions, try/catch
*/
// ES3 (1999) - Modern JavaScript begins
// Regular expressions
var pattern = /[a-z]+/gi;
var text = "Hello World";
var matches = text.match(pattern);
// Try/catch error handling
try {
riskyOperation();
} catch (error) {
console.log("Error: " + error.message);
}
// Array methods
var numbers = [1, 2, 3, 4, 5];
var doubled = [];
for (var i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
/* The Dark Age:
* ES4 proposed (2000-2008) - Too ambitious, abandoned
* Browser inconsistencies plague developers
* Libraries emerge to paper over differences (Prototype, jQuery)
*/
AJAX and new browser engines revitalize JavaScript.
/* Key Milestones:
* 2005: Jesse James Garrett coins "AJAX"
* 2006: jQuery released - "Write less, do more"
* 2008: Google Chrome + V8 engine (blazing fast)
* 2009: Node.js released - JavaScript on the server
* 2009: ECMAScript 5 (ES5) - JSON, strict mode, getters/setters
*/
// AJAX revolution (2005)
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
updateUI(data);
}
};
xhr.send();
// ES5 (2009) features
"use strict"; // Strict mode
var person = {
firstName: "John",
lastName: "Doe",
// Getter
get fullName() {
return this.firstName + " " + this.lastName;
},
// Setter
set fullName(name) {
var parts = name.split(" ");
this.firstName = parts[0];
this.lastName = parts[1];
}
};
// Array methods
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map(function(n) { return n * 2; });
var evens = numbers.filter(function(n) { return n % 2 === 0; });
// JSON native support
var json = JSON.stringify({ name: "Alice", age: 30 });
var obj = JSON.parse(json);
ES6/ES2015 transforms JavaScript into a truly modern language.
/* ES6/ES2015 - Biggest update ever:
* - Arrow functions
* - Classes
* - Modules
* - Template literals
* - Destructuring
* - Promises
* - let/const
* - Spread/rest operators
* - Default parameters
*/
// Arrow functions
const doubled = numbers.map(n => n * 2);
// Template literals
const name = "Alice";
const greeting = `Hello, ${name}!`;
// Destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
const { name: userName, age } = user;
// Classes
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
// Promises
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// ES2017 - Async/await
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
// ES2020 - Optional chaining & nullish coalescing
const street = user?.address?.street;
const port = config.port ?? 3000;
// ES2022 - Private fields
class Counter {
#count = 0; // Private
increment() {
this.#count++;
}
getCount() {
return this.#count;
}
}
Key events that shaped JavaScript's evolution.
/* JavaScript Timeline:
*
* 1995: Brendan Eich creates JavaScript in 10 days
* 1996: Microsoft releases JScript in IE 3.0
* 1997: ECMAScript 1 standardized
* 1999: ES3 - try/catch, regex, Array methods
* 2005: AJAX coined - Web 2.0 begins
* 2006: jQuery released - DOM manipulation simplified
* 2008: Google Chrome + V8 engine launched
* 2009: Node.js - JavaScript on the server
* 2009: ES5 - JSON, strict mode, getters/setters
* 2010: AngularJS released by Google
* 2013: React released by Facebook
* 2014: Vue.js released
* 2015: ES6/ES2015 - Modern JavaScript begins
* 2016: Yarn package manager
* 2017: ES2017 - async/await
* 2018: npm reaches 1 million packages
* 2019: Optional chaining proposal advances
* 2020: ES2020 - Optional chaining, nullish coalescing
* 2021: Rome toolchain announced
* 2022: ES2022 - Private fields, top-level await
* 2023: ES2023 - Array methods (toSorted, toReversed)
* 2024+: Yearly updates continue
*/
// Evolution of writing the same code:
// 1999 (ES3)
function getUsers() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users', false); // Synchronous!
xhr.send();
return JSON.parse(xhr.responseText);
}
// 2009 (ES5)
function getUsers(callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users', true);
xhr.onload = function() {
callback(JSON.parse(xhr.responseText));
};
xhr.send();
}
// 2015 (ES6)
function getUsers() {
return fetch('/api/users')
.then(response => response.json());
}
// 2017 (ES2017)
async function getUsers() {
const response = await fetch('/api/users');
return await response.json();
}
// 2020 (ES2020)
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user?.profile?.name ?? 'Anonymous';
}
JavaScript's ecosystem has exploded beyond the language itself.
/* Key Ecosystem Components:
*
* Package Managers:
* - npm (2010) - 2M+ packages
* - Yarn (2016) - Faster, deterministic
* - pnpm (2017) - Disk space efficient
*
* Build Tools:
* - Webpack (2012) - Module bundler
* - Parcel (2017) - Zero config
* - Vite (2020) - Lightning fast
* - Turbopack (2022) - Rust-based
*
* Frameworks:
* - jQuery (2006) - DOM manipulation
* - AngularJS (2010) - Full framework
* - React (2013) - Component UI
* - Vue (2014) - Progressive framework
* - Svelte (2016) - Compiled framework
* - Solid (2021) - Reactive primitives
*
* Server Runtimes:
* - Node.js (2009) - V8-based
* - Deno (2020) - Secure by default
* - Bun (2022) - All-in-one toolkit
*
* TypeScript (2012):
* - Static typing for JavaScript
* - Catches errors at compile time
* - Powers VS Code, Angular
*/
// TypeScript example
interface User {
id: number;
name: string;
email: string;
}
async function getUser(id: number): Promise {
const response = await fetch(`/api/users/${id}`);
return await response.json();
}
Now that you understand JavaScript's history, move on to Getting Started to write your first JavaScript code, or explore Variables & Constants to learn modern syntax.
Learn how statements, expressions, and blocks fit together to make readable, maintainable code.
JavaScript syntax is flexible but opinionated. Understanding how statements end, how blocks group logic, and how automatic semicolon insertion (ASI) works helps you avoid subtle bugs. Layer clear identifiers, comments, and strict mode for safer programs.
Statements perform actions; expressions produce values. Knowing the difference clarifies where each is valid.
let total; // declaration statement
const user = getUser(); // assignment statement
if (user.active) { // if statement
logIn(user);
}
for (let i = 0; i < 3; i++) { // loop statement
console.log(i);
}
const price = 25 * 1.08; // arithmetic expression
const label = `Total: $${price}`; // template literal expression
const active = user && user.online; // logical expression
sendEmail(active ? user.email : 'support@example.com'); // ternary expression
// Expressions can appear inside larger expressions
const score = (hits / totalShots) * 100;
Curly braces create blocks that define scope for let and const. Keep blocks tight and purposeful.
function showStatus(status) {
if (!status) {
return 'Unknown';
}
{
// inner block for temporary variables
const label = status.toUpperCase();
console.log('Logging label', label);
}
return `Status: ${status}`;
}
console.log(showStatus('online'));
const callbacks = [];
for (let i = 0; i < 3; i++) {
callbacks.push(() => console.log('i is', i));
}
callbacks.forEach(fn => fn()); // 0, 1, 2 thanks to block-scoped let
JavaScript inserts semicolons automatically in many places, but not all. Write consistent semicolons to avoid edge cases.
const items = [];
items.push('a');
items.push('b');
console.log(items);
// Avoid leading parentheses on new lines after return
function risky() {
return // ASI inserts semicolon here
{
value: 1
};
}
console.log(risky()); // undefined
const nums = [1, 2, 3];
// Works
[1, 2, 3].forEach(n => console.log(n));
// Breaks if written after a return without semicolon
function broken() {
return
[1, 2, 3].map(n => n * 2); // ASI inserts semicolon before array
}
console.log(broken()); // undefined
Use comments to explain why, not what. Prefer short, actionable notes and remove stale comments quickly.
// TODO: add retry with backoff when network fails
const MAX_RETRIES = 3;
/*
* Use a block comment for multi-line explanations or API contracts.
* Keep it concise and keep code self-documenting.
*/
function fetchUser(id) {
return api.get(`/users/${id}`);
}
/**
* Calculate an average, ignoring nullish values.
* @param {number[]} values
* @returns {number}
*/
function average(values) {
const valid = values.filter(v => v ?? false);
return valid.reduce((sum, v) => sum + v, 0) / valid.length;
}
Identifiers name variables and functions. Avoid reserved keywords and choose descriptive, consistent names.
const userCount = 42;
let isConnected = true;
function loadProfile(userId) {
return api.fetch(`/users/${userId}`);
}
// Avoid single-letter names except for small scopes (i, j in loops)
// Examples of reserved words: class, import, export, return, if, while
// Do not use them as identifiers
// const class = 'nope'; // SyntaxError
const ClassName = 'ok';
const importedValue = 10;
'use strict' opts into a safer subset of JavaScript: it catches silent errors, disallows implicit globals, and reserves future keywords.
'use strict';
function assign() {
// next line would throw ReferenceError instead of creating a global
// oops = 123;
return true;
}
assign();
// ES modules are strict by default
export function greet(name) {
return `Hello, ${name}`;
}
// CommonJS: enable explicitly
function legacy() {
'use strict';
return this === undefined;
}
console.log(legacy()); // true
Group related logic into modules and functions. Keep files focused and expose a clear API.
// math/utils.js
export function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
export function average(values) {
return values.reduce((sum, n) => sum + n, 0) / values.length;
}
// app.js
import { clamp, average } from './math/utils.js';
const temperatures = [68, 72, 70, 75];
console.log('Average', average(temperatures));
console.log('Clamped', clamp(120, 60, 100));
Consistent formatting reduces mental overhead. Pick a style (prettier, eslint configs) and stick to it across files.
// Prefer const for values that never reassign
// Use single quotes or double quotes consistently
// Indent blocks with 2 spaces or tabs consistently
// Place imports at the top, exports at the bottom
const URL_BASE = 'https://api.example.com';
function getUrl(path) {
return `${URL_BASE}${path}`;
}
// .eslintrc.js snippet
module.exports = {
env: { browser: true, es2021: true },
extends: ['eslint:recommended'],
rules: {
'no-unused-vars': 'warn',
'semi': ['error', 'always'],
'quotes': ['error', 'single']
}
};
return and fix it with explicit semicolons.let block scope differs from var in a loop.let/const rather than var.return and before arrays.Continue to Control Flow to see how syntax powers branching and decisions, or jump to Loops and Iteration for repeated execution patterns.
Master modern declarations with let and const, understand scoping, and avoid subtle hoisting pitfalls.
Bindings are the foundation of every program. Knowing when to choose let, when to lock values with const, and when to avoid var will keep your code predictable and maintainable.
Default to const for values that should not be reassigned. Use let when you truly need reassignment inside a block.
const apiUrl = '/v1'; // stable reference
let retryCount = 0; // will change
retryCount += 1; // ok
// apiUrl = '/v2'; // TypeError: Assignment to constant variable
Variables declared with let and const live inside their nearest block ({ ... }). This prevents accidental leaks into surrounding code.
for (let i = 0; i < 3; i++) {
const label = `step-${i}`;
console.log(label);
}
// console.log(i); // ReferenceError: i is not defined
// console.log(label); // ReferenceError: label is not defined
var declarations are hoisted and initialized to undefined, which can hide bugs. let and const are hoisted too but stay in the temporal dead zone until the declaration runs.
console.log(total); // undefined (var is hoisted)
var total = 5;
// console.log(count); // ReferenceError (TDZ for let)
let count = 5;
The TDZ prevents access to let and const bindings before their declaration. This enforces clear ordering and reduces runtime surprises.
function createUser(name) {
// Accessing user before declaration would throw
const user = { name, createdAt: new Date() };
return user;
}
// const output = user; // ReferenceError if uncommented before declaration
const output = createUser('Dev');
console.log(output);
Use clear, intention-revealing names. Prefer nouns for data (userProfile), verbs for actions (calculateTotal), and uppercase constants for configuration values you never expect to change at runtime.
const MAX_RETRIES = 3;
let currentRetry = 0;
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
const total = calculateTotal([
{ name: 'Book', price: 12 },
{ name: 'Pen', price: 2 }
]);
console.log(total);
const prevents reassignment of the binding, not mutation of the value. You can still change properties or array items.
const user = { name: 'Kai', role: 'author' };
user.role = 'editor'; // allowed
// user = {}; // TypeError: Assignment to constant variable
const tags = ['js', 'web'];
tags.push('es6'); // allowed
console.log(tags.join(', '));
Legacy code may still use var. Understand its function scope and hoisting so you can refactor safely.
function legacyFlag(condition) {
if (condition) {
var flag = true; // function-scoped, leaks outside the block
}
return flag; // returns true or undefined
}
function saferFlag(condition) {
if (!condition) return false;
const flag = true; // block-scoped
return flag;
}
Reassign only when it communicates state change, such as counters, accumulators, or temporary swaps. Otherwise, favor immutable patterns.
// Reassign with let when accumulating
let totalMinutes = 0;
for (const session of [30, 45, 25]) {
totalMinutes += session;
}
// Prefer const when using map/filter/reduce
const durations = [30, 45, 25];
const doubled = durations.map(minutes => minutes * 2);
console.log({ totalMinutes, doubled });
Use destructuring to pull fields from objects and arrays while keeping scoping clear. Default to const unless mutation is required.
const userProfile = {
name: 'Riley',
email: 'riley@example.com',
plan: 'pro'
};
const { name, plan } = userProfile;
console.log(name, plan);
let [first, second] = ['alpha', 'beta'];
[first, second] = [second, first]; // swap with reassignment
console.log(first, second);
Shadowing happens when an inner scope reuses a name from an outer scope. Keep it intentional and short-lived to avoid confusion.
const status = 'global';
function showStatus() {
const status = 'function'; // shadows outer
if (true) {
let status = 'block'; // shadows function-level
console.log(status); // block
}
console.log(status); // function
}
showStatus();
console.log(status); // global
Use Object.freeze to prevent accidental mutations to critical configuration objects while still benefiting from const bindings.
const CONFIG = Object.freeze({
apiBase: 'https://api.example.com',
timeoutMs: 5000
});
// CONFIG.apiBase = 'https://malicious.site'; // no effect in strict mode, throws in strict
console.log(CONFIG.apiBase);
// Note: freeze is shallow; nest carefully
const SAFE_CONFIG = Object.freeze({
headers: Object.freeze({ Accept: 'application/json' })
});
console.log(SAFE_CONFIG.headers.Accept);
const can appear in loops because each iteration creates a new binding. This keeps values stable within that single pass while avoiding accidental reuse.
const ids = [101, 102, 103];
for (const id of ids) {
// id is a new binding on every iteration
const message = `Processing ${id}`;
console.log(message);
}
// Using let for counters when you mutate the value
for (let index = 0; index < ids.length; index++) {
console.log('Index', index, 'Id', ids[index]);
}
var and convert it to let/const, confirming behavior stays the same.const and update one of its properties safely.let binding before its declaration to observe the ReferenceError, then fix the order.let to swap two variables without a temporary placeholder.const by default; reach for let only when reassignment is required.let and const are block-scoped, preventing accidental leaks.var is function-scoped and hoisted; refactor it out in modern code.const protects the binding, not the contents of objects or arrays.const and let for both objects and arrays.Move on to data types and operations to learn how these bindings interact with strings, numbers, and objects across your applications.
Understand primitives, objects, and how JavaScript checks and coerces values in real applications.
JavaScript values come in two broad families: primitives and objects. Knowing how they behave, how to detect them, and how coercion works will prevent subtle bugs in APIs, forms, and calculations.
Strings represent text and support interpolation with template literals, making dynamic messages concise.
const name = 'Nova';
const greeting = `Hello, ${name}!`;
console.log(greeting);
const multiline = `Line one
Line two
Line three`;
console.log(multiline);
Numbers cover integers and floats. Be mindful of division by zero, invalid math, and floating-point quirks.
const price = 9.99;
const quantity = 3;
const total = price * quantity; // 29.97
console.log(1 / 0); // Infinity
console.log(Math.sqrt(-1)); // NaN
console.log(Number.isNaN(NaN)); // true
// Floating point precision
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(Number((0.1 + 0.2).toFixed(2))); // 0.3
Booleans are strictly true or false, but many values implicitly convert in conditions. Understand truthy/falsy to write predictable checks.
const inputs = ['text', '', 0, 42, null, undefined, [], {}];
inputs.forEach(value => {
if (value) {
console.log(value, 'is truthy');
} else {
console.log(value, 'is falsy');
}
});
null is an intentional empty value you set. undefined usually means “not provided” or “not initialized.”
let user;
console.log(user); // undefined
const profile = {
name: 'Avi',
bio: null // intentionally empty until user writes one
};
console.log(profile.bio === null); // true
console.log('age' in profile); // false
console.log(profile.missingProp); // undefined
Symbols create unique keys that avoid collisions, useful for metadata or library internals.
const id = Symbol('id');
const user = { name: 'Ivy', [id]: 12345 };
console.log(user[id]); // 12345
console.log(Object.keys(user)); // ['name'] — symbol is hidden from keys
Use BigInt when numbers exceed safe integer limits. Add n to the end of literals or call BigInt().
const maxSafe = Number.MAX_SAFE_INTEGER; // 9007199254740991
const bigger = BigInt(maxSafe) + 10n;
console.log(maxSafe + 10); // still 9007199254741000 (precision risk)
console.log(bigger); // 9007199254741001n
Objects store keyed collections; arrays store ordered lists. Both are reference types, so assignments copy references, not values.
const original = { city: 'Lagos' };
const alias = original; // same reference
alias.city = 'Nairobi';
console.log(original.city); // Nairobi
const list = ['alpha', 'beta'];
const copy = [...list]; // shallow copy with spread
copy.push('gamma');
console.log(list, copy);
typeof works for primitives but returns "object" for arrays and null. Combine it with helpers like Array.isArray.
console.log(typeof 'hi'); // string
console.log(typeof 42); // number
console.log(typeof null); // object (historical quirk)
console.log(Array.isArray([])); // true
console.log(typeof Symbol('x')); // symbol
console.log(typeof 10n); // bigint
function isPlainObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
console.log(isPlainObject({})); // true
console.log(isPlainObject(new Date())); // false
JavaScript will coerce types in arithmetic and comparisons. Prefer strict equality and explicit casts to avoid surprises.
console.log('5' + 1); // "51" (string concatenation)
console.log('5' - 1); // 4 (string coerced to number)
console.log(Boolean('')); // false
console.log(Boolean('hi')); // true
console.log(0 == false); // true (coercion)
console.log(0 === false); // false (no coercion)
const amount = Number('42'); // explicit conversion
console.log(amount + 8); // 50
Primitives copy by value; objects copy by reference. Deep comparison requires custom logic or utility libraries.
const a = 5;
const b = a; // value copy
const obj1 = { theme: 'dark', lang: 'en' };
const obj2 = { ...obj1 }; // shallow copy
console.log(a === b); // true
console.log(obj1 === obj2); // false (different references)
// Simple deep clone for JSON-safe objects
const clone = JSON.parse(JSON.stringify(obj1));
console.log(clone);
Dates are objects, not primitives. JSON serialization turns objects into strings; numbers and strings survive intact, but functions and symbols are ignored.
const now = new Date();
console.log(now.toISOString());
console.log(now.getFullYear());
// Avoid storing dates as strings if you need math; keep Date instances or timestamps
const timestamp = now.getTime();
console.log(new Date(timestamp));
const payload = {
id: 7,
active: true,
createdAt: new Date().toISOString(),
tags: ['js', 'learning']
};
const json = JSON.stringify(payload); // object -> string
console.log(json);
const parsed = JSON.parse(json); // string -> object
console.log(parsed.tags[0]);
// Functions and symbols drop during serialization
console.log(JSON.stringify({ fn: () => 'hi', sym: Symbol('x') })); // {}
typeof result for every field in an object that mixes strings, numbers, booleans, and arrays.== and rewrite them with ===, noting behavioral changes.Number.MAX_SAFE_INTEGER and represent it safely with BigInt.null when data is intentionally empty and undefined when a property is missing.typeof works for primitives but needs helpers for arrays and null.null signals intentional emptiness; undefined means missing or uninitialized.Proceed to expressions and operators to see how these data types combine in calculations, conditions, and real application flows.
Combine values with operators to compute results, compare data, and control program flow.
Expressions produce values; operators transform or combine those values. Mastering the operator toolkit helps you read and write concise, intentional code across arithmetic, comparison, logical reasoning, and newer capabilities like optional chaining and nullish coalescing.
Use arithmetic to compute totals, percentages, and formatted strings. Watch out for integer vs floating-point quirks.
const subtotal = 49.99 * 3;
const taxRate = 0.0825;
const tax = +(subtotal * taxRate).toFixed(2); // force numeric with unary +
const total = subtotal + tax;
const perItem = total / 3;
const remainder = 17 % 5; // 2
const exponent = 2 ** 5; // 32
const discount = total - 5; // subtract a coupon
console.log({ subtotal, tax, total, perItem, remainder, exponent, discount });
let stock = 10;
console.log(stock++); // 10 (post-increment returns old value)
console.log(stock); // 11
let seats = 5;
console.log(++seats); // 6 (pre-increment returns new value)
console.log(seats); // 6
let countdown = 3;
while (countdown--) {
console.log('Launching in', countdown);
}
Compound assignments shorten updates and clarify intent, especially when mutating counters or accumulating totals.
let balance = 1000;
balance += 250; // deposit
balance -= 90; // purchase
balance *= 1.05; // interest
balance /= 2; // split between accounts
let flags = 0b0011;
flags |= 0b1000; // set bit 4
flags &= 0b0101; // mask bits
const user = { name: 'Sam', role: 'admin', active: true };
const { name, role, active = false } = user;
const rgb = [255, 128, 64];
const [red, green, blue, alpha = 1] = rgb;
console.log(name, role, active);
console.log(red, green, blue, alpha);
Strict comparisons avoid implicit type coercion. Use === and !== for predictable results.
console.log(2 == '2'); // true (coerced)
console.log(2 === '2'); // false (strict)
console.log(null == undefined); // true
console.log(null === undefined); // false
const age = 18;
const canVote = age >= 18;
const isTeen = age >= 13 && age <= 19;
console.log({ canVote, isTeen });
console.log('apple' < 'banana'); // true
console.log('Z' > 'a'); // false (uppercase sorts before lowercase)
const words = ['delta', 'alpha', 'charlie'];
const sorted = [...words].sort();
console.log(sorted); // ['alpha', 'charlie', 'delta']
Logical operators short-circuit, making them perfect for fallbacks and guards. Nullish coalescing and optional chaining reduce noisy checks.
const cached = null;
const fromApi = () => 'live-data';
const result = cached || fromApi(); // OR returns first truthy
const mustBeReady = true && 'go'; // AND returns last value if all truthy
console.log({ result, mustBeReady });
const user = { settings: { theme: 'dark' } };
const theme = user && user.settings && user.settings.theme;
console.log(theme); // 'dark'
const config = { api: { retries: 0, timeout: 5000 } };
const retries = config.api?.retries ?? 3; // 0 is respected
const timeout = config.api?.timeout ?? 3000; // 5000
const locale = config.user?.preferences?.locale ?? 'en-US';
console.log({ retries, timeout, locale });
const response = { data: null };
const message = response.data?.title ?? 'No data yet';
console.log(message);
The ternary operator packs concise conditional expressions. Pair with guard clauses for readable branches.
const score = 82;
const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : 'C';
const label = score >= 70 ? 'Pass' : 'Retake';
const status = isLoggedIn
? `Welcome back, ${user.name}`
: 'Please sign in';
console.log({ grade, label, status });
function sendEmail(user) {
if (!user?.email) return 'Missing email';
if (!user.verified) return 'Verify account first';
// main path
return `Sent to ${user.email}`;
}
console.log(sendEmail({ email: 'a@example.com', verified: true }));
console.log(sendEmail({ email: null, verified: true }));
Bitwise operators toggle flags efficiently. They work on 32-bit integers and are common in permissions and feature toggles.
const CAN_READ = 1 << 0; // 0001
const CAN_WRITE = 1 << 1; // 0010
const CAN_DELETE = 1 << 2; // 0100
let permissions = 0;
permissions |= CAN_READ;
permissions |= CAN_WRITE;
const canDelete = (permissions & CAN_DELETE) !== 0;
const canWrite = (permissions & CAN_WRITE) !== 0;
console.log({ canDelete, canWrite });
console.log(~5); // bitwise NOT => -6
console.log(5 & 3); // 1 (0101 & 0011)
console.log(5 | 3); // 7 (0101 | 0011)
console.log(5 ^ 3); // 6 (0101 ^ 0011)
// Truncate to 32-bit integer
console.log(3.9 | 0); // 3
Spread copies iterable values into new arrays or objects. Rest collects the remaining values into an array.
const original = [1, 2, 3];
const extended = [...original, 4, 5];
const defaults = { theme: 'light', compact: false };
const overrides = { compact: true };
const settings = { ...defaults, ...overrides };
console.log({ extended, settings });
function sum(label, ...numbers) {
const total = numbers.reduce((acc, n) => acc + n, 0);
return `${label}: ${total}`;
}
const stats = sum('Points', 12, 8, 15, 20);
console.log(stats);
const [first, ...others] = ['alpha', 'beta', 'gamma'];
console.log(first, others);
Check primitive types with typeof and object inheritance with instanceof. Remember arrays are objects.
console.log(typeof 42); // 'number'
console.log(typeof 'hi'); // 'string'
console.log(typeof null); // 'object' (historical quirk)
console.log(typeof undefined); // 'undefined'
console.log(typeof (() => {})); // 'function'
console.log(Array.isArray([])); // true
class Animal {}
class Dog extends Animal {}
const rover = new Dog();
console.log(rover instanceof Dog); // true
console.log(rover instanceof Animal); // true
console.log(rover instanceof Object); // true
const date = new Date();
console.log(date instanceof Date); // true
console.log(date instanceof Object); // true
Operator precedence controls evaluation order. Use parentheses to clarify complex expressions and avoid surprises.
const result = 4 + 3 * 2; // 10, multiplication first
const grouped = (4 + 3) * 2; // 14
const chain = 1 || 0 && false; // 1, AND before OR
const safe = (1 || 0) && false; // false
console.log({ result, grouped, chain, safe });
const price = 120;
const coupon = 10;
const member = true;
const final = (price - coupon) * (member ? 0.9 : 1);
const label = `Final: $${final.toFixed(2)}`;
console.log(label);
// Compose logical and arithmetic
const isEligible = price > 100 && member;
console.log({ isEligible });
sum(...numbers) function with rest parameters and test it with different lengths.typeof checks for primitives and instanceof checks for custom classes.a && b() and a || b().typeof targets primitives, while instanceof checks prototype inheritance.Move on to JavaScript Syntax and Structure to see how operators fit inside statements, or jump to Control Flow to combine expressions with branching.
Guide your program with branches, guards, and structured error handling.
Control flow lets you decide what runs and when. Combine if/else, switch, ternaries, and guard clauses to keep logic readable. Understand truthy/falsy values and handle errors with try/catch/finally to build resilient code.
Use if for simple branching. Chain else if for ranges and keep branches minimal.
function grade(score) {
if (score >= 90) {
return 'A';
} else if (score >= 80) {
return 'B';
} else if (score >= 70) {
return 'C';
}
return 'Needs work';
}
console.log(grade(85));
// Deep nesting hurts readability
function access(user) {
if (user) {
if (user.active) {
if (user.role === 'admin') {
return 'full access';
}
}
}
return 'limited access';
}
// Flatten with early returns
function accessFlat(user) {
if (!user) return 'limited access';
if (!user.active) return 'limited access';
if (user.role !== 'admin') return 'limited access';
return 'full access';
}
Ternaries compress small decisions into expressions. Guard clauses return early to prevent deep nesting.
const points = 120;
const tier = points > 200 ? 'gold' : points > 100 ? 'silver' : 'bronze';
const label = points >= 100 ? 'Qualified' : 'Pending';
console.log({ tier, label });
function sendBonus(user) {
if (!user?.email) return 'No email on file';
if (!user.active) return 'Inactive user';
return `Bonus sent to ${user.email}`;
}
console.log(sendBonus({ email: 'a@example.com', active: true }));
switch shines when many discrete cases share logic. Always include a default branch.
function shippingCost(zone) {
switch (zone) {
case 'local':
return 5;
case 'regional':
case 'national':
return 10; // fallthrough groups cases
case 'international':
return 25;
default:
return 15;
}
}
console.log(shippingCost('regional'));
function getBadge(score) {
switch (true) {
case score >= 90: return 'Platinum';
case score >= 75: return 'Gold';
case score >= 60: return 'Silver';
default: return 'Bronze';
}
}
console.log(getBadge(78));
JavaScript treats some values as false in conditionals. Know the list and use nullish checks when zero or empty strings are valid.
const falsyValues = [false, 0, -0, '', null, undefined, NaN];
falsyValues.forEach(value => {
if (value) {
console.log('truthy? nope');
} else {
console.log('falsy detected');
}
});
console.log(Boolean('hello')); // true
console.log(Boolean([])); // true (arrays are truthy)
const attempts = 0; // valid zero
const retries = attempts ?? 3; // keeps 0
const response = { data: null };
const content = response.data ?? 'Loading...';
console.log({ retries, content });
Combine optional chaining with nullish coalescing to safely read nested properties without crashes.
const session = { user: { profile: { name: 'Ravi' } } };
const name = session.user?.profile?.name ?? 'Guest';
const city = session.user?.profile?.address?.city ?? 'Unknown';
console.log({ name, city });
const analytics = {
track(event) {
console.log('Tracking', event);
}
};
analytics.track?.('page_view');
const maybeTrack = null;
maybeTrack?.('noop'); // safe no-op
Wrap risky code to recover gracefully. Use finally for cleanup that must always run.
function parseJson(input) {
try {
return JSON.parse(input);
} catch (error) {
console.error('Could not parse JSON', error.message);
return null;
}
}
console.log(parseJson('{"ok":true}'));
console.log(parseJson('bad')); // null
function readConfig(load) {
let config;
try {
config = load();
if (!config.enabled) throw new Error('Disabled');
return config;
} catch (error) {
console.warn('Fallback to defaults', error.message);
return { enabled: false };
} finally {
console.log('Config attempted');
}
}
readConfig(() => ({ enabled: true }));
readConfig(() => { throw new Error('Missing file'); });
Early returns reduce indentation and highlight happy paths. Combine with validation for cleaner control flow.
function processOrder(order) {
if (!order) return 'Missing order';
if (!order.items?.length) return 'Empty cart';
if (order.total <= 0) return 'Total invalid';
// happy path
return `Processing ${order.items.length} items`;
}
console.log(processOrder({ items: ['a', 'b'], total: 50 }));
function canPublish(user) {
const hasRole = user?.role === 'editor' || user?.role === 'admin';
const isTrusted = Boolean(user?.verified) && !user.suspended;
return hasRole && isTrusted;
}
console.log(canPublish({ role: 'editor', verified: true, suspended: false }));
if chain using guard clauses to flatten the structure.switch that groups multiple cases together and includes a default.0, null, and undefined using nullish coalescing.if and when it should not.if statement.try/catch and log a friendly error message.finally by releasing a resource (e.g., clearing a timer) regardless of success.switch for multi-branch decisions and include a default case.try/catch/finally to recover gracefully and clean up.Continue to Loops and Iteration to repeat work efficiently, or review Operators and Expressions to strengthen the building blocks of your conditions.
Repeat work efficiently with classic loops, array iterators, and control statements.
Iteration patterns shape performance and readability. Choose the loop that matches your data and termination conditions. Modern array helpers like map, filter, and reduce encourage declarative thinking, while break, continue, and labels help you escape or skip work precisely.
Use the indexed for loop when you need the index, custom step sizes, or early exits.
const logs = [];
for (let i = 0; i < 5; i++) {
logs.push(`Step ${i}`);
}
console.log(logs);
for (let i = 10; i >= 0; i -= 2) {
console.log('Countdown', i);
}
const numbers = [1, 3, 5, 8, 9];
let firstEven = null;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
firstEven = numbers[i];
break; // stop searching
}
}
console.log(firstEven); // 8
while loops run while a condition remains true. do...while runs at least once.
let attempts = 0;
while (attempts < 3) {
console.log('Attempt', attempts + 1);
attempts++;
}
let password;
do {
password = prompt('Enter password');
} while (!password);
console.log('Password captured');
for...of walks through iterable values like arrays, strings, Maps, and Sets. It is concise and readable.
const fruits = ['apple', 'banana', 'cherry'];
for (const fruit of fruits) {
console.log(fruit.toUpperCase());
}
const sentence = 'loop';
for (const char of sentence) {
console.log(char);
}
const map = new Map([
['id', 3],
['name', 'Asha']
]);
for (const [key, value] of map) {
console.log(key, value);
}
const set = new Set([1, 2, 2, 3]);
for (const value of set) {
console.log(value); // 1, 2, 3
}
for...in iterates enumerable keys. Use it for plain objects, not arrays, to avoid unexpected order.
const user = { name: 'Lee', role: 'editor', active: true };
for (const key in user) {
if (Object.hasOwn(user, key)) {
console.log(key, user[key]);
}
}
const arr = ['x', 'y', 'z'];
for (const index in arr) {
console.log(index); // '0', '1', '2' (strings)
}
// Prefer for...of or array helpers for arrays
Array methods express iteration declaratively. forEach executes side effects; map transforms.
const emails = [];
['a@x.com', 'b@x.com', 'c@x.com'].forEach((email, index) => {
emails.push({ id: index + 1, email });
});
console.log(emails);
// Note: break/continue do not work with forEach
const prices = [5, 10, 20];
const withTax = prices.map(price => ({
price,
total: +(price * 1.08).toFixed(2)
}));
console.log(withTax);
Filter arrays to matching elements, locate a single element, or check predicates across the whole collection.
const users = [
{ name: 'Ava', active: true },
{ name: 'Bo', active: false },
{ name: 'Cal', active: true }
];
const activeUsers = users.filter(u => u.active);
const firstInactive = users.find(u => !u.active);
const allActive = users.every(u => u.active);
const someoneInactive = users.some(u => !u.active);
console.log({ activeUsers, firstInactive, allActive, someoneInactive });
const products = [
{ name: 'Pen', price: 1.5, tags: ['office'] },
{ name: 'Notebook', price: 4, tags: ['office', 'paper'] },
{ name: 'Marker', price: 2, tags: ['office'] }
];
const underThree = products.filter(p => p.price < 3);
const paperRelated = products.filter(p => p.tags.includes('paper'));
console.log({ underThree, paperRelated });
reduce combines array values into a single result: numbers, objects, or even promises.
const totals = [5, 10, 15];
const sum = totals.reduce((acc, value) => acc + value, 0);
console.log(sum); // 30
const orders = [
{ status: 'shipped', id: 1 },
{ status: 'pending', id: 2 },
{ status: 'shipped', id: 3 }
];
const grouped = orders.reduce((acc, order) => {
acc[order.status] = acc[order.status] || [];
acc[order.status].push(order.id);
return acc;
}, {});
console.log(grouped);
Control loop execution manually. Labels help exit nested loops, but use them sparingly for clarity.
for (let i = 1; i <= 5; i++) {
if (i % 2 === 0) continue; // skip even numbers
console.log('Odd', i);
}
outer: for (let row = 0; row < 3; row++) {
for (let col = 0; col < 3; col++) {
if (row === col) {
console.log('Diagonal found', row, col);
break outer; // exits both loops
}
}
}
for loop that counts down by 3s and stops at zero.for...of to iterate a string and collect vowel counts.for...in while guarding with Object.hasOwn.forEach and note why break will not work.map, filter, and reduce to compute totals.do...while by prompting until valid input arrives.break and compare to find.continue skips specific iterations in a validation loop.break to exit nested loops when a target cell is found.for loop into array helpers and note trade-offs.for for indices, for...of for iterables, for...in for object keys.map, filter, and reduce encourage declarative, side-effect-free transformations.break and continue shape control inside loops; labels should be rare and intentional.while and do...while suit unknown iteration counts or at-least-once flows.Head to Functions to package loop logic into reusable pieces, or revisit Operators and Expressions to refine the calculations inside your iterations.
Define reusable logic with declarations, expressions, and modern parameter patterns.
Functions are the building blocks of JavaScript programs. They encapsulate behavior, accept inputs, and return outputs. Explore declarations, expressions, arrow functions, parameters, defaults, rest, closures, and immediately invoked function expressions (IIFEs) to write expressive, modular code.
Declarations are hoisted; expressions are assigned to variables and can be passed around freely.
function greet(name) {
return `Hello, ${name}`;
}
console.log(greet('Mia'));
const greetUser = function(name) {
return `Welcome, ${name}`;
};
console.log(greetUser('Dev'));
Arrow functions are concise and lexically bind this. They are great for callbacks and small utilities.
const double = n => n * 2;
const formatName = (first, last) => `${first} ${last}`;
const describe = (user) => {
const role = user.role ?? 'guest';
return `${user.name} (${role})`;
};
console.log(double(4));
console.log(formatName('Ada', 'Lovelace'));
console.log(describe({ name: 'Nia', role: 'admin' }));
const counter = {
value: 0,
increment() {
setInterval(() => {
this.value++;
console.log('Value', this.value);
}, 1000);
}
};
counter.increment(); // arrow preserves outer this
IIFEs run as soon as they are defined. They create a private scope for setup code.
const settings = (() => {
const secret = 'token-123';
const baseUrl = 'https://api.example.com';
return { baseUrl, getToken: () => secret };
})();
console.log(settings.getToken());
(async () => {
const response = await fetch('https://api.quotable.io/random');
const data = await response.json();
console.log('Quote', data.content);
})();
Provide default values, collect variable arguments, and destructure parameters for clarity.
function createUser({ name, role = 'viewer', active = true }) {
return { name, role, active };
}
console.log(createUser({ name: 'Rita' }));
console.log(createUser({ name: 'Vic', role: 'editor', active: false }));
function sum(label, ...values) {
const total = values.reduce((acc, n) => acc + n, 0);
return `${label}: ${total}`;
}
console.log(sum('Scores', 10, 15, 20));
Return values explicitly. Use early returns to handle invalid states quickly.
function divide(a, b) {
if (b === 0) return null;
return a / b;
}
console.log(divide(10, 2));
console.log(divide(10, 0));
function sendNotification(user) {
if (!user?.email) return 'No email';
if (!user.optedIn) return 'No consent';
return `Sent to ${user.email}`;
}
console.log(sendNotification({ email: 'x@y.com', optedIn: true }));
Recursion solves problems by calling the function inside itself. Always include a base case to avoid infinite loops.
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
const tree = {
value: 'root',
children: [
{ value: 'a', children: [] },
{ value: 'b', children: [{ value: 'c', children: [] }] }
]
};
function visit(node) {
console.log(node.value);
node.children.forEach(visit);
}
visit(tree);
Closures capture variables from outer scopes, enabling private state and configurable functions.
function makeCounter() {
let count = 0;
return () => {
count++;
return count;
};
}
const next = makeCounter();
console.log(next());
console.log(next());
function makeMultiplier(factor) {
return number => number * factor;
}
const doubleNum = makeMultiplier(2);
const tripleNum = makeMultiplier(3);
console.log(doubleNum(5));
console.log(tripleNum(5));
Pass functions as arguments, return them from other functions, and store them in data structures.
function applyTwice(fn, value) {
return fn(fn(value));
}
const increment = n => n + 1;
console.log(applyTwice(increment, 3)); // 5
const strategies = {
json: data => JSON.stringify(data),
text: data => String(data)
};
const format = (type, payload) => strategies[type]?.(payload) ?? '';
console.log(format('json', { ok: true }));
console.log(format('text', 42));
this inside a method.[1, [2, [3]]]).this lexically; use them for callbacks and small utilities.Proceed to Arrays to manipulate collections with the functions you write, or revisit Loops and Iteration to see how functions and iteration pair together.
Understand where variables live and how declarations move during compilation.
JavaScript scopes control visibility. Declarations are processed before execution (hoisting), but var behaves differently from let, const, and functions. Knowing lexical scope, the temporal dead zone, and closures prevents subtle bugs.
var is function-scoped; let and const are block-scoped. Globals live on window in browsers.
var greeting = 'hello';
function run() {
var greeting = 'hi';
console.log(greeting);
}
run();
console.log(greeting);
function setFlag() {
flag = true; // implicit global (avoid)
}
setFlag();
console.log(window.flag);
let and const respect block boundaries like if and for.
if (true) {
let scoped = 'inside';
const fixed = 42;
}
// console.log(scoped); // ReferenceError
// console.log(fixed); // ReferenceError
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log('let', i), 0);
}
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log('var', j), 0);
}
Inner scopes can access outer variables; the reverse is not true.
const outer = 'outside';
function parent() {
const mid = 'middle';
function child() {
const inner = 'inside';
console.log(outer, mid, inner);
}
child();
}
parent();
function parent() {
const secret = 'hidden';
}
// console.log(secret); // ReferenceError
Declarations are hoisted to the top of their scope. Initializations are not.
console.log(total); // undefined due to hoisting
var total = 10;
// Behind the scenes:
// var total;
// console.log(total);
// total = 10;
// console.log(count); // ReferenceError: TDZ
let count = 5;
const limit = 10;
console.log(count, limit);
Between block start and declaration, let/const exist but cannot be accessed.
function demo(condition) {
if (condition) {
// console.log(message); // TDZ
const message = 'ready';
console.log(message);
}
}
demo(true);
Function declarations are hoisted fully; function expressions obey variable hoisting.
run();
function run() {
console.log('runs');
}
// runLater(); // TypeError: runLater is not a function
var runLater = function() {
console.log('later');
};
runLater();
Closures capture lexical variables, enabling private state and deferred execution.
function makeCounter() {
let value = 0;
return function() {
value++;
return value;
};
}
const inc = makeCounter();
console.log(inc());
console.log(inc());
function fetchWithLog(url) {
const started = Date.now();
return async function() {
const ms = Date.now() - started;
console.log(`Requesting after ${ms}ms`);
const res = await fetch(url);
return res.json();
};
}
const load = fetchWithLog('/api');
// later
// load();
'use strict' prevents silent global creation and enforces safer semantics.
'use strict';
function markReady() {
// status = 'ready'; // ReferenceError
let status = 'ready';
return status;
}
console.log(markReady());
Avoid re-declaring var in the same scope and be aware of hoisted undefined values.
let value = 1;
function sample() {
console.log(value);
let value = 2; // TDZ hides outer value
}
// sample(); // ReferenceError
function configure() {
var mode = 'a';
var mode = 'b'; // allowed but risky
return mode;
}
console.log(configure());
var and let inside a for loop with setTimeout.var, then repeat with let and compare errors.let binding before declaration inside a block.var is function-scoped; let/const are block-scoped and live in the temporal dead zone until initialized.undefined or ReferenceErrors.Move on to Prototypes and Inheritance to see how scope chains support object behavior, or revisit Destructuring, Spread, and Rest to practice reshaping scoped variables.
Store ordered data, transform collections, and work with array-focused utilities.
Arrays are ordered lists that power most data flows in JavaScript. Learn to create them, access items, and use built-in methods for inserting, removing, slicing, and transforming. Mastering array helpers like map, filter, and reduce leads to cleaner, declarative data pipelines.
Construct arrays with literals or constructors. Access elements by zero-based index, and use length to measure size.
const empty = [];
const numbers = [1, 2, 3];
const mixed = ['text', 99, true, { ok: true }];
console.log(numbers[0]); // 1
console.log(mixed[mixed.length - 1]); // { ok: true }
const set = new Set([1, 2, 3]);
const fromSet = [...set];
const fromString = [...'hello'];
console.log(fromSet);
console.log(fromString);
Mutating methods adjust the array in place. Be aware when mutation is acceptable in your codebase.
const queue = ['first'];
queue.push('second'); // ['first', 'second']
queue.unshift('start'); // ['start', 'first', 'second']
const last = queue.pop(); // removes 'second'
const first = queue.shift(); // removes 'start'
console.log(queue, first, last);
const tools = ['pencil', 'pen', 'marker'];
tools.splice(1, 1, 'eraser', 'ruler'); // replace pen with two items
console.log(tools); // ['pencil', 'eraser', 'ruler', 'marker']
tools.splice(2, 0, 'sharpener'); // insert without removal
console.log(tools);
tools.splice(-1, 1); // remove last item
console.log(tools);
Prefer non-mutating methods when sharing arrays. slice, concat, and spread make safe copies.
const animals = ['cat', 'dog', 'bird', 'fish'];
const firstTwo = animals.slice(0, 2); // ['cat', 'dog']
const withoutFirst = animals.slice(1); // ['dog', 'bird', 'fish']
const combined = firstTwo.concat(['lizard']);
console.log(firstTwo, withoutFirst, combined);
console.log(animals); // original unchanged
const base = [1, 2, 3];
const copy = [...base];
const merged = [...base, 4, ...[5, 6]];
console.log(copy, merged);
console.log(base);
Use length to size arrays and helpers like includes and indexOf for membership tests.
const tasks = ['draft', 'review', 'ship'];
console.log(tasks.length); // 3
console.log(tasks.includes('review')); // true
console.log(tasks.indexOf('ship')); // 2
console.log(tasks.indexOf('missing')); // -1
const todos = [
{ id: 1, done: false },
{ id: 2, done: true }
];
const firstDone = todos.find(t => t.done);
const doneIndex = todos.findIndex(t => t.done);
console.log(firstDone, doneIndex);
map, filter, and reduce create new collections or single values without mutation.
const scores = [80, 92, 67];
const curved = scores.map(s => s + 5);
const passed = scores.filter(s => s >= 70);
console.log({ curved, passed });
const totals = scores.reduce((sum, s) => sum + s, 0);
const average = totals / scores.length;
console.log({ totals, average });
Locate elements quickly with find, some, and every. These short-circuit when possible.
const flags = [true, true, false];
console.log(flags.some(Boolean)); // true
console.log(flags.every(Boolean)); // false
const catalog = [
{ sku: 'A1', stock: 0 },
{ sku: 'B2', stock: 3 },
{ sku: 'C3', stock: 5 }
];
const firstInStock = catalog.find(item => item.stock > 0);
console.log(firstInStock);
sort and reverse mutate arrays; copy first if you need to preserve originals.
const values = [10, 2, 30, 25];
const sorted = [...values].sort((a, b) => a - b);
const reversed = [...sorted].reverse();
console.log({ values, sorted, reversed });
const names = ['Åsa', 'Anna', 'Özil'];
const collated = [...names].sort((a, b) => a.localeCompare(b));
console.log(collated);
Use flat and flatMap to compress nested arrays. Choose a depth that matches your data.
const nested = [1, [2, [3, 4]]];
console.log(nested.flat(1)); // [1, 2, [3, 4]]
console.log(nested.flat(2)); // [1, 2, 3, 4]
const products = [
{ name: 'pen', tags: ['stationery', 'office'] },
{ name: 'tape', tags: ['office'] }
];
const tags = products.flatMap(p => p.tags);
console.log(tags); // ['stationery', 'office', 'office']
const logs = [
'info:started',
'warn:slow',
'info:finished'
];
const warnings = logs
.filter(line => line.startsWith('warn'))
.map(line => line.split(':')[1])
.flat();
console.log(warnings);
Spread and slice copy arrays. Avoid mutating shared arrays unless necessary; prefer returning new arrays.
const state = ['todo'];
const nextState = [...state, 'in-progress'];
console.log(state, nextState);
// Remove without mutation
const filtered = nextState.filter(status => status !== 'todo');
console.log(filtered);
// Replace value immutably
const replaced = nextState.map(status => status === 'todo' ? 'done' : status);
console.log(replaced);
const deep = [{ id: 1, tags: ['a'] }];
const cloned = deep.map(item => ({ ...item, tags: [...item.tags] }));
cloned[0].tags.push('b');
console.log(deep[0].tags); // ['a']
console.log(cloned[0].tags); // ['a', 'b']
push/pop/shift/unshift to manage a queue and log its state.splice to insert, replace, and remove items at different positions.concat or spread.map and filter it with filter; compute totals with reduce.find and its position with findIndex.flat and merge mapping plus flattening with flatMap.filter, map, and reduce to summarize data.length.push, pop, splice) cautiously; prefer non-mutating copies with slice, concat, or spread.map, filter, reduce, and search with find, some, every.sort and reverse mutate; copy first if you need the original order.flat and flatMap simplify nested structures; clone nested arrays to avoid unintended side effects.Combine arrays with Functions to build reusable data pipelines, or revisit Control Flow to decide when those pipelines run.
Model real-world entities with properties, methods, and flexible shapes.
Objects are key-value maps that let you bundle related data and behavior. Learn literals, methods, the this keyword, computed properties, cloning patterns, and built-in helpers like Object.keys, Object.values, and Object.entries to work confidently with structured data.
Create objects with literal syntax and access properties using dot or bracket notation.
const user = {
name: 'Alex',
age: 28,
role: 'editor',
active: true
};
console.log(user.name);
console.log(user['role']);
user.location = 'Remote';
user['theme'] = 'dark';
user.age = 29;
delete user.active;
console.log(Object.keys(user));
Functions stored on objects are methods. this points to the owning object when using method syntax.
const account = {
owner: 'Jordan',
balance: 500,
deposit(amount) {
this.balance += amount;
return this.balance;
},
withdraw(amount) {
if (amount > this.balance) return 'Insufficient';
this.balance -= amount;
return this.balance;
}
};
console.log(account.deposit(150));
console.log(account.withdraw(200));
const withdraw = account.withdraw;
console.log(withdraw(50)); // this is undefined in strict mode
const safeWithdraw = account.withdraw.bind(account);
console.log(safeWithdraw(50));
Use shorthand when property names match variable names, and compute keys dynamically.
const title = 'Engineer';
const level = 'Senior';
const profile = { title, level, location: 'NYC' };
console.log(profile);
const metric = 'pageViews';
const stats = {
[metric]: 1200,
['last-update']: new Date().toISOString()
};
console.log(stats.pageViews);
console.log(stats['last-update']);
Model deeper structures and avoid crashes with optional chaining and nullish coalescing.
const product = {
id: 'sku-1',
details: {
name: 'Desk',
price: 199,
dimensions: { width: 120, height: 75 }
},
stock: {
warehouse: 10,
store: 2
}
};
console.log(product.details.dimensions.width);
console.log(product.stock?.store ?? 0);
console.log(product.supplier?.name ?? 'Unknown');
Use Object.assign or spread to copy and combine objects without mutating sources.
const base = { ready: true, retries: 0 };
const copyA = Object.assign({}, base, { retries: 1 });
const copyB = { ...base, retries: 2 };
console.log(copyA, copyB);
const defaults = { cache: true, timeout: 3000 };
const overrides = { timeout: 5000, headers: { Accept: 'application/json' } };
const config = { ...defaults, ...overrides };
console.log(config);
Iterate over keys and values to transform data structures.
const settings = { theme: 'dark', language: 'en', beta: false };
const keys = Object.keys(settings);
const values = Object.values(settings);
const entries = Object.entries(settings);
console.log(keys);
console.log(values);
const mapped = entries.map(([key, value]) => `${key}=${value}`);
console.log(mapped.join('; '));
Pull properties into variables with defaults, renaming, and rest properties.
const customer = { id: 7, name: 'Sam', plan: 'pro' };
const { name, plan } = customer;
console.log(name, plan);
const response = { status: 200 };
const { status: code, message = 'OK' } = response;
console.log(code, message);
const { plan: tier, ...rest } = customer;
console.log(tier, rest);
Prevent accidental mutations by freezing or creating new copies instead of altering originals.
const env = Object.freeze({ mode: 'prod', version: '1.0.0' });
env.mode = 'dev';
console.log(env.mode); // still prod
const addTag = (item, tag) => ({ ...item, tags: [...(item.tags ?? []), tag] });
const note = { title: 'Todo', tags: ['urgent'] };
console.log(addTag(note, 'today'));
console.log(note);
Factories return new object instances with private state via closures.
function createCounter(label) {
let value = 0;
return {
label,
increment() {
value++;
return `${label}: ${value}`;
},
reset() {
value = 0;
}
};
}
const visits = createCounter('visits');
console.log(visits.increment());
console.log(visits.increment());
visits.reset();
console.log(visits.increment());
const canLog = state => ({
log(message) {
state.history.push(message);
return state.history;
}
});
const canToggle = state => ({
toggle() {
state.enabled = !state.enabled;
return state.enabled;
}
});
const createFeature = name => {
const state = { name, enabled: false, history: [] };
return { ...state, ...canLog(state), ...canToggle(state) };
};
const feature = createFeature('Search');
console.log(feature.toggle());
console.log(feature.log('Initialized'));
this to update a balance; test what happens when the method is detached.Object.entries to convert an object to query-string format.this for context.Object.assign create shallow copies; avoid mutating originals.Object.keys, Object.values, and Object.entries help iterate and transform objects.Continue with Destructuring, Spread, and Rest to unpack and reassemble data, or revisit Functions to deepen how behavior is attached to objects.
Unpack, clone, and recombine data with expressive ES6 syntax.
Destructuring lets you pull values from arrays and objects into variables. Spread copies elements into new collections, while rest gathers remaining items. Master these together to simplify assignments, function parameters, and data transformations.
Assign array items to variables by position. Skip items with commas and set defaults for missing values.
const colors = ['red', 'green', 'blue'];
const [primary, secondary, tertiary] = colors;
console.log(primary, secondary, tertiary);
const response = ['ok'];
const [status, message = 'No message'] = response;
const [, secondColor] = ['cyan', 'magenta', 'yellow'];
console.log(status, message);
console.log(secondColor);
Swap variables without a temp and unpack nested structures in one statement.
let left = 'A';
let right = 'B';
[left, right] = [right, left];
console.log(left, right);
const grid = [[1, 2], [3, 4]];
const [[topLeft], [, bottomRight]] = grid;
console.log(topLeft, bottomRight);
Pick properties by name, rename them locally, and provide defaults.
const user = { id: 9, name: 'Kai', role: 'admin' };
const { name, role: jobTitle } = user;
console.log(name, jobTitle);
const settings = { theme: 'dark', layout: { sidebar: true } };
const { theme = 'light', layout: { sidebar = false, direction = 'ltr' } } = settings;
console.log(theme, sidebar, direction);
Collect remaining items from arrays or properties from objects.
const [first, ...others] = ['ui', 'api', 'db', 'ops'];
console.log(first);
console.log(others);
const team = { lead: 'Sam', qa: 'Lee', dev: 'Ash', pm: 'Jo' };
const { lead, ...crew } = team;
console.log(lead);
console.log(crew);
Expand arrays or objects into new ones for cloning, merging, and inserting.
const baseStack = ['HTML', 'CSS'];
const stack = [...baseStack, 'JavaScript', 'TypeScript'];
const copy = [...stack];
console.log(stack);
console.log(copy);
const defaults = { retry: 3, cache: true };
const env = { cache: false };
const config = { ...defaults, ...env, headers: { Accept: 'application/json' } };
console.log(config);
Destructure parameters to document required fields and apply defaults at the call boundary.
function createUser({ name, role = 'viewer', active = true }) {
return { name, role, active };
}
console.log(createUser({ name: 'Mira' }));
function logTopTwo([first, second]) {
console.log('Top:', first, second);
}
logTopTwo(['alpha', 'beta', 'gamma']);
Combine destructuring, spread, and rest for expressive data manipulation.
const apiResponse = {
data: { items: [{ id: 1 }, { id: 2 }], total: 2 },
meta: { page: 1, pageSize: 10 }
};
const {
data: { items, total },
meta: { page }
} = apiResponse;
console.log(items, total, page);
const core = ['login', 'logout'];
const extras = ['profile', 'billing'];
const features = ['home', ...core, 'search', ...extras];
const [, firstFeature, ...restFeatures] = features;
console.log(features);
console.log(firstFeature, restFeatures);
Provide defaults when destructuring possibly undefined values to avoid runtime errors.
const config = null;
const { mode = 'prod', retries = 1 } = config ?? {};
console.log(mode, retries);
const payload = {};
const {
user: {
name = 'guest',
preferences: { theme = 'light' } = {}
} = {}
} = payload;
console.log(name, theme);
Use rest on the last position only and be mindful of shallow copies with spread.
const original = { nested: { count: 1 } };
const clone = { ...original };
clone.nested.count = 5;
console.log(original.nested.count); // 5 because spread is shallow
Destructure directly in loop headers or parameter lists to keep bodies focused.
const entries = [
['theme', 'dark'],
['lang', 'en']
];
for (const [key, value] of entries) {
console.log(key, value);
}
function renderUser({ profile: { name }, stats: { followers = 0 } = {} }) {
return `${name} (${followers} followers)`;
}
console.log(renderUser({ profile: { name: 'Mona' }, stats: { followers: 10 } }));
Head to Scope and Hoisting to see where variables live, or jump back to Objects to practice combining these patterns with real data structures.
Modern string interpolation and tagged templates
Create strings that span multiple lines without escape characters or concatenation.
// Old way - difficult to read
const oldMultiline = 'This is line 1\n' +
'This is line 2\n' +
'This is line 3';
// Template literals - clean and intuitive
const newMultiline = `This is line 1
This is line 2
This is line 3`;
console.log(newMultiline);
// HTML templates
const htmlTemplate = `
<div class="container">
<h1>Welcome</h1>
<p>This is a paragraph</p>
</div>
`;
// SQL queries
const sqlQuery = `
SELECT users.name, orders.total
FROM users
INNER JOIN orders ON users.id = orders.user_id
WHERE orders.status = 'completed'
ORDER BY orders.total DESC
`;
// Email templates
const emailBody = `
Dear Customer,
Thank you for your order!
Your order will be delivered soon.
Best regards,
The Team
`;
// Code snippets
const codeExample = `
function greet(name) {
return \`Hello, \${name}!\`;
}
`;
// Preserves indentation
const indented = `
Level 1
Level 2
Level 3
`;
console.log(indented); // Indentation is preserved
// Remove leading indentation
function dedent(str) {
const lines = str.split('\n');
const minIndent = Math.min(
...lines
.filter(line => line.trim())
.map(line => line.search(/\S/))
);
return lines
.map(line => line.slice(minIndent))
.join('\n')
.trim();
}
const code = `
function hello() {
console.log('world');
}
`;
console.log(dedent(code));
Embed expressions directly in strings with ${} syntax for dynamic content.
// Basic interpolation
const name = 'John';
const age = 30;
console.log(`Hello, ${name}!`);
console.log(`${name} is ${age} years old`);
// Expressions
const a = 10;
const b = 20;
console.log(`The sum is ${a + b}`); // 'The sum is 30'
console.log(`Double: ${a * 2}`); // 'Double: 20'
// Function calls
function getGreeting() {
return 'Good morning';
}
console.log(`${getGreeting()}, ${name}!`);
// Method calls
const user = {
firstName: 'John',
lastName: 'Doe',
fullName() {
return `${this.firstName} ${this.lastName}`;
}
};
console.log(`Welcome, ${user.fullName()}!`);
// Ternary operators
const isAdmin = true;
console.log(`Role: ${isAdmin ? 'Administrator' : 'User'}`);
const score = 85;
console.log(`Grade: ${score >= 90 ? 'A' : score >= 80 ? 'B' : 'C'}`);
// Object properties
const product = {
name: 'Laptop',
price: 999,
discount: 0.1
};
console.log(`${product.name}: $${product.price * (1 - product.discount)}`);
// Array operations
const numbers = [1, 2, 3, 4, 5];
console.log(`Sum: ${numbers.reduce((a, b) => a + b, 0)}`);
console.log(`Items: ${numbers.join(', ')}`);
// Date formatting
const now = new Date();
console.log(`Today is ${now.toLocaleDateString()}`);
// Template in template
const nested = `Outer ${`Inner ${name}`}`;
console.log(nested); // 'Outer Inner John'
// Conditional rendering
const showDetails = true;
const message = `
Name: ${name}
${showDetails ? `Age: ${age}` : ''}
`;
// Complex expressions
const items = ['apple', 'banana', 'orange'];
const list = `
<ul>
${items.map(item => `<li>${item}</li>`).join('\n ')}
</ul>
`;
console.log(list);
Combine multiple template literals for complex data structures and UI components.
// Component pattern
function Card({ title, content, footer }) {
return `
<div class="card">
<div class="card-header">
<h3>${title}</h3>
</div>
<div class="card-body">
${content}
</div>
${footer ? `
<div class="card-footer">
${footer}
</div>
` : ''}
</div>
`;
}
const card = Card({
title: 'Welcome',
content: 'This is the content',
footer: 'Last updated: Today'
});
// List rendering
function UserList(users) {
return `
<ul class="user-list">
${users.map(user => `
<li class="user-item">
<span class="user-name">${user.name}</span>
<span class="user-email">${user.email}</span>
</li>
`).join('')}
</ul>
`;
}
const users = [
{ name: 'John', email: 'john@example.com' },
{ name: 'Jane', email: 'jane@example.com' }
];
console.log(UserList(users));
// Table generation
function Table(data, columns) {
return `
<table>
<thead>
<tr>
${columns.map(col => `<th>${col.label}</th>`).join('')}
</tr>
</thead>
<tbody>
${data.map(row => `
<tr>
${columns.map(col => `<td>${row[col.key]}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
`;
}
const tableData = [
{ name: 'John', age: 30, city: 'NYC' },
{ name: 'Jane', age: 25, city: 'LA' }
];
const tableColumns = [
{ key: 'name', label: 'Name' },
{ key: 'age', label: 'Age' },
{ key: 'city', label: 'City' }
];
console.log(Table(tableData, tableColumns));
// Navigation menu
function Nav(items) {
return `
<nav>
<ul>
${items.map(item => `
<li>
<a href="${item.url}" ${item.active ? 'class="active"' : ''}>
${item.label}
</a>
${item.children ? `
<ul class="submenu">
${item.children.map(child => `
<li><a href="${child.url}">${child.label}</a></li>
`).join('')}
</ul>
` : ''}
</li>
`).join('')}
</ul>
</nav>
`;
}
const navItems = [
{ label: 'Home', url: '/', active: true },
{
label: 'Products',
url: '/products',
children: [
{ label: 'Electronics', url: '/products/electronics' },
{ label: 'Clothing', url: '/products/clothing' }
]
}
];
console.log(Nav(navItems));
Tagged templates allow custom processing of template literals with tag functions.
// Basic tagged template
function tag(strings, ...values) {
console.log('Strings:', strings); // Array of string parts
console.log('Values:', values); // Array of interpolated values
return strings.reduce((result, str, i) => {
return result + str + (values[i] || '');
}, '');
}
const name = 'John';
const age = 30;
const result = tag`Hello ${name}, you are ${age} years old`;
// Uppercase interpolations
function uppercase(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? String(values[i]).toUpperCase() : '');
}, '');
}
console.log(uppercase`Hello ${name}!`); // 'Hello JOHN!'
// Currency formatting
function currency(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i];
const formatted = typeof value === 'number'
? `$${value.toFixed(2)}`
: value || '';
return result + str + formatted;
}, '');
}
const price = 19.99;
console.log(currency`The price is ${price}`); // 'The price is $19.99'
// Safe HTML escaping
function html(strings, ...values) {
const escape = (str) => {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
};
return strings.reduce((result, str, i) => {
const value = values[i];
const escaped = value != null ? escape(String(value)) : '';
return result + str + escaped;
}, '');
}
const userInput = '<script>alert("XSS")</script>';
const safe = html`<div>${userInput}</div>`;
console.log(safe); // Escaped, safe HTML
// Styled components pattern
function css(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] || '');
}, '');
}
const primaryColor = '#007bff';
const styles = css`
.button {
background-color: ${primaryColor};
padding: 10px 20px;
border-radius: 4px;
}
`;
// Localization/i18n
function t(strings, ...values) {
// Simplified translation function
const translations = {
'Hello': 'Hola',
'Goodbye': 'Adiós'
};
return strings.reduce((result, str, i) => {
const translated = translations[str.trim()] || str;
return result + translated + (values[i] || '');
}, '');
}
console.log(t`Hello ${name}!`);
// SQL query builder (NEVER use with user input!)
function sql(strings, ...values) {
// This is for demonstration only - use parameterized queries in production!
return strings.reduce((query, str, i) => {
const value = values[i];
const escaped = typeof value === 'string'
? `'${value.replace(/'/g, "''")}'`
: value;
return query + str + (escaped || '');
}, '');
}
const userId = 123;
const query = sql`SELECT * FROM users WHERE id = ${userId}`;
// Debug logger
function debug(strings, ...values) {
const timestamp = new Date().toISOString();
const message = strings.reduce((result, str, i) => {
return result + str + (values[i] !== undefined ? JSON.stringify(values[i]) : '');
}, '');
console.log(`[${timestamp}] DEBUG: ${message}`);
return message;
}
debug`User ${name} logged in at ${new Date()}`;
Prevent XSS attacks by properly escaping user-generated content in templates.
// Escape HTML entities
function escapeHTML(str) {
const escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return String(str).replace(/[&<>"'/]/g, (char) => escapeMap[char]);
}
// Safe template tag
function safeHTML(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i];
const escaped = value != null ? escapeHTML(value) : '';
return result + str + escaped;
}, '');
}
// DANGEROUS - Never do this!
const userComment = '<script>alert("XSS")</script>';
// const dangerous = `<div>${userComment}</div>`;
// SAFE - Always escape user input
const safe = safeHTML`<div>${userComment}</div>`;
console.log(safe); // <div><script>alert("XSS")</script></div>
// Allow specific HTML tags
function sanitizeHTML(html, allowedTags = ['b', 'i', 'em', 'strong']) {
const div = document.createElement('div');
div.innerHTML = html;
// Remove all elements except allowed
const allElements = div.querySelectorAll('*');
allElements.forEach(el => {
if (!allowedTags.includes(el.tagName.toLowerCase())) {
el.replaceWith(document.createTextNode(el.textContent));
}
});
return div.innerHTML;
}
const mixedHTML = '<b>Bold</b> <script>alert("XSS")</script> <i>Italic</i>';
console.log(sanitizeHTML(mixedHTML)); // '<b>Bold</b> alert("XSS") <i>Italic</i>'
// URL encoding for attributes
function encodeURL(url) {
return encodeURIComponent(url);
}
function safeLink(url, text) {
return `<a href="${encodeURL(url)}">${escapeHTML(text)}</a>`;
}
const userURL = 'javascript:alert("XSS")';
console.log(safeLink(userURL, 'Click me')); // Safe
// Content Security Policy headers
const cspTemplate = `
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
`;
// Safe JSON embedding
function safeJSON(data) {
return JSON.stringify(data)
.replace(//g, '\\u003e')
.replace(/&/g, '\\u0026');
}
const jsonData = { message: '</script><script>alert("XSS")</script>' };
const safeScript = `
<script>
const data = ${safeJSON(jsonData)};
</script>
`;
Protect against injection attacks in SQL queries and CSS styles.
// SQL injection prevention (ALWAYS use parameterized queries in production!)
function safeSQLParam(value) {
if (typeof value === 'number') {
return value;
}
if (typeof value === 'string') {
// Escape single quotes
return `'${value.replace(/'/g, "''")}'`;
}
if (value === null) {
return 'NULL';
}
throw new Error('Unsupported type');
}
// DON'T DO THIS - vulnerable to SQL injection
// const username = "admin' OR '1'='1";
// const badQuery = `SELECT * FROM users WHERE username = '${username}'`;
// DO THIS - use parameterized queries
// const safeQuery = db.query('SELECT * FROM users WHERE username = ?', [username]);
// CSS injection prevention
function safeCSS(value) {
// Remove potentially dangerous characters
return String(value).replace(/[<>"';\(\)]/g, '');
}
function generateStyle(color, size) {
return `
.custom {
color: ${safeCSS(color)};
font-size: ${safeCSS(size)}px;
}
`;
}
// JavaScript code injection prevention
function safeJavaScript(value) {
// Escape quotes and newlines
return String(value)
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r');
}
function generateScript(message) {
return `
<script>
const message = '${safeJavaScript(message)}';
console.log(message);
</script>
`;
}
// Whitelist validation
function validateColor(color) {
const validColors = ['red', 'blue', 'green', 'black', 'white'];
return validColors.includes(color) ? color : 'black';
}
// Pattern validation
function validateHexColor(color) {
return /^#[0-9A-Fa-f]{6}$/.test(color) ? color : '#000000';
}
// Safe URL construction
function buildURL(base, params) {
const url = new URL(base);
Object.keys(params).forEach(key => {
url.searchParams.append(key, params[key]);
});
return url.toString();
}
const safeURL = buildURL('https://api.example.com/search', {
q: 'user input',
page: 1
});
Production-ready patterns for component libraries and templating systems.
// Template cache for performance
const templateCache = new Map();
function cachedTemplate(key, generator) {
if (templateCache.has(key)) {
return templateCache.get(key);
}
const template = generator();
templateCache.set(key, template);
return template;
}
// Component system
class Component {
constructor(props) {
this.props = props;
}
render() {
return `<div>Override this method</div>`;
}
mount(selector) {
const container = document.querySelector(selector);
container.innerHTML = this.render();
}
}
class Button extends Component {
render() {
const { label, onClick, variant = 'primary' } = this.props;
return `
<button
class="btn btn-${variant}"
onclick="${onClick}"
>
${label}
</button>
`;
}
}
// Usage
const button = new Button({
label: 'Click Me',
onClick: 'handleClick()',
variant: 'success'
});
button.mount('#app');
// Template inheritance
function Layout(title, content) {
return `
<!DOCTYPE html>
<html>
<head>
<title>${title}</title>
</head>
<body>
<header>Site Header</header>
<main>
${content}
</main>
<footer>Site Footer</footer>
</body>
</html>
`;
}
function Page(title, body) {
return Layout(title, `
<article>
<h1>${title}</h1>
${body}
</article>
`);
}
// Conditional rendering helper
function renderIf(condition, template) {
return condition ? template : '';
}
// Loop rendering helper
function renderEach(items, template) {
return items.map(template).join('');
}
// Usage
const todos = [
{ id: 1, text: 'Learn JS', done: true },
{ id: 2, text: 'Build app', done: false }
];
const todoList = `
<ul>
${renderEach(todos, todo => `
<li class="${todo.done ? 'done' : ''}">
${todo.text}
${renderIf(todo.done, '<span>Done</span>')}
</li>
`)}
</ul>
`;
// Template composition
function compose(...templates) {
return templates.join('\n');
}
const page = compose(
'<header>Header</header>',
'<main>Content</main>',
'<footer>Footer</footer>'
);
Master the Document Object Model and dynamic page updates
Modern methods like querySelector and querySelectorAll provide powerful, flexible element selection using CSS selectors.
// querySelector - returns first match
const header = document.querySelector('h1');
const button = document.querySelector('#submitBtn');
const firstItem = document.querySelector('.list-item');
const nestedElement = document.querySelector('div.container > p');
// querySelectorAll - returns NodeList of all matches
const allButtons = document.querySelectorAll('button');
const allItems = document.querySelectorAll('.list-item');
// Convert NodeList to Array for array methods
const itemsArray = Array.from(allItems);
const itemsSpread = [...allItems];
itemsArray.forEach(item => console.log(item.textContent));
// Legacy methods (still useful)
const byId = document.getElementById('myId'); // Faster than querySelector
const byClass = document.getElementsByClassName('myClass'); // Live HTMLCollection
const byTag = document.getElementsByTagName('div'); // Live HTMLCollection
// Difference: querySelector returns static, getElements returns live
const liveList = document.getElementsByClassName('item');
const staticList = document.querySelectorAll('.item');
console.log(liveList.length); // e.g., 3
document.body.innerHTML += '<div class="item">New</div>';
console.log(liveList.length); // 4 (updated automatically)
console.log(staticList.length); // Still 3 (static snapshot)
// Complex selectors
const complexSelect = document.querySelector('ul li:nth-child(2)');
const attributeSelect = document.querySelector('input[type="email"]');
const notSelector = document.querySelectorAll('div:not(.exclude)');
// Selecting within elements (scoped queries)
const container = document.querySelector('.container');
const innerButton = container.querySelector('button'); // Only searches within container
// Closest - find nearest ancestor matching selector
const listItem = document.querySelector('.item');
const parentList = listItem.closest('ul');
const parentContainer = listItem.closest('.container');
// Matches - check if element matches selector
if (button.matches('.primary')) {
console.log('This is a primary button');
}
Dynamically create, insert, and remove DOM elements to build interactive interfaces.
// Create elements
const div = document.createElement('div');
const paragraph = document.createElement('p');
const span = document.createElement('span');
// Set content and attributes
div.className = 'container';
div.id = 'myContainer';
paragraph.textContent = 'Hello, World!';
// Append to DOM
document.body.appendChild(div);
div.appendChild(paragraph);
// Modern methods: append (can take multiple nodes and strings)
div.append(span, ' More text', paragraph);
// prepend - add to beginning
div.prepend('First: ');
// before/after - insert before/after element
const existingEl = document.querySelector('#existing');
existingEl.before(div);
existingEl.after(span);
// replaceWith - replace element
const oldEl = document.querySelector('.old');
const newEl = document.createElement('div');
newEl.textContent = 'New element';
oldEl.replaceWith(newEl);
// insertAdjacentHTML - insert HTML at specific position
const list = document.querySelector('ul');
list.insertAdjacentHTML('beforebegin', '<h3>List Title</h3>');
list.insertAdjacentHTML('afterbegin', '<li>First Item</li>');
list.insertAdjacentHTML('beforeend', '<li>Last Item</li>');
list.insertAdjacentHTML('afterend', '<p>After list</p>');
// insertAdjacentElement and insertAdjacentText also available
const newItem = document.createElement('li');
newItem.textContent = 'Inserted Item';
list.insertAdjacentElement('beforeend', newItem);
// Remove elements
const elementToRemove = document.querySelector('.remove-me');
elementToRemove.remove(); // Modern
// Legacy removal
// elementToRemove.parentNode.removeChild(elementToRemove);
// Remove all children
const parent = document.querySelector('.parent');
parent.innerHTML = ''; // Simple but loses event listeners
// OR
while (parent.firstChild) {
parent.removeChild(parent.firstChild); // Safer
}
// OR
parent.replaceChildren(); // Modern, clean way
// Clone elements
const original = document.querySelector('.original');
const clone = original.cloneNode(true); // true = deep clone (with children)
document.body.appendChild(clone);
// Create document fragment for batch operations
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li); // Doesn't trigger reflow
}
// Single DOM operation
document.querySelector('ul').appendChild(fragment);
Understanding the difference is crucial for security and performance.
const element = document.querySelector('.content');
// textContent - plain text only (SAFE)
element.textContent = 'Hello, World!';
element.textContent = '<strong>Bold</strong>'; // Displays as plain text
console.log(element.textContent); // Gets all text content
// innerHTML - parses HTML (XSS risk!)
element.innerHTML = '<strong>Bold</strong>'; // Renders as HTML
element.innerHTML = '<p>Paragraph</p><p>Another</p>';
// DANGER: Never use user input directly
const userInput = '<img src=x onerror="alert(\'XSS\')">';
// element.innerHTML = userInput; // DON'T DO THIS!
// Safe alternative - escape HTML
function escapeHTML(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
const safeHTML = escapeHTML(userInput);
element.innerHTML = safeHTML; // Now safe
// innerText vs textContent
element.textContent = 'Some text'; // Faster, doesn't trigger reflow
element.innerText = 'Some text'; // Respects CSS, slower
// Example difference
const hidden = document.querySelector('.hidden'); // CSS: display: none
console.log(hidden.textContent); // Returns text
console.log(hidden.innerText); // Returns empty string
// outerHTML - includes element itself
console.log(element.outerHTML); // <div class="content">...</div>
element.outerHTML = '<section>New element</section>'; // Replaces element
// insertAdjacentHTML for safer HTML insertion
function addNotification(message) {
const container = document.querySelector('#notifications');
const safeMessage = escapeHTML(message);
container.insertAdjacentHTML('beforeend', `
<div class="notification">
<span>${safeMessage}</span>
<button class="close">x</button>
</div>
`);
}
// Performance comparison
const container = document.querySelector('.container');
// SLOW: Multiple reflows
for (let i = 0; i < 1000; i++) {
container.innerHTML += `<div>${i}</div>`;
}
// FAST: Single reflow
let html = '';
for (let i = 0; i < 1000; i++) {
html += `<div>${i}</div>`;
}
container.innerHTML = html;
// FASTEST: DOM methods with fragment
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = i;
frag.appendChild(div);
}
container.appendChild(frag);
Manipulate CSS classes efficiently with the classList API.
const element = document.querySelector('.box');
// Add classes
element.classList.add('active');
element.classList.add('highlighted', 'important'); // Multiple at once
// Remove classes
element.classList.remove('inactive');
element.classList.remove('old', 'deprecated');
// Toggle class
element.classList.toggle('open'); // Adds if absent, removes if present
// Toggle with condition
const isActive = true;
element.classList.toggle('active', isActive); // Adds if true, removes if false
// Check if class exists
if (element.classList.contains('active')) {
console.log('Element is active');
}
// Replace class
element.classList.replace('old-theme', 'new-theme');
// Get all classes
console.log(element.classList); // DOMTokenList
console.log([...element.classList]); // Array of class names
// Iterate classes
element.classList.forEach(className => {
console.log(className);
});
// Legacy className (less convenient)
element.className = 'box active'; // Replaces all classes
element.className += ' new-class'; // Append (watch the space!)
console.log(element.className); // String of space-separated classes
// Practical: State management
class Component {
constructor(element) {
this.element = element;
}
setLoading(isLoading) {
this.element.classList.toggle('loading', isLoading);
this.element.classList.toggle('ready', !isLoading);
}
setError(hasError) {
this.element.classList.toggle('error', hasError);
}
setState(states) {
// Remove all state classes
this.element.classList.remove('loading', 'success', 'error');
// Add new state
Object.keys(states).forEach(state => {
if (states[state]) {
this.element.classList.add(state);
}
});
}
}
const component = new Component(document.querySelector('.widget'));
component.setState({ loading: true });
// Later...
component.setState({ success: true });
Read and modify element attributes, including custom data attributes.
const link = document.querySelector('a');
const input = document.querySelector('input');
// Get attributes
const href = link.getAttribute('href');
const type = input.getAttribute('type');
// Set attributes
link.setAttribute('href', 'https://example.com');
input.setAttribute('placeholder', 'Enter email');
// Remove attributes
link.removeAttribute('target');
// Check if attribute exists
if (link.hasAttribute('download')) {
console.log('Download attribute present');
}
// Direct property access (preferred for standard attributes)
link.href = 'https://example.com';
input.value = 'test@example.com';
input.disabled = true;
// Boolean attributes
input.disabled = true;
input.required = true;
input.readOnly = true;
// Data attributes (custom attributes)
// HTML: <div data-user-id="123" data-user-name="John">
const userEl = document.querySelector('[data-user-id]');
// Access via dataset (camelCase)
console.log(userEl.dataset.userId); // "123"
console.log(userEl.dataset.userName); // "John"
// Set data attributes
userEl.dataset.userRole = 'admin';
userEl.dataset.lastLogin = new Date().toISOString();
// Remove data attribute
delete userEl.dataset.userName;
// Practical: Store component state
const button = document.querySelector('.toggle-btn');
button.dataset.expanded = 'false';
button.addEventListener('click', () => {
const isExpanded = button.dataset.expanded === 'true';
button.dataset.expanded = !isExpanded;
button.textContent = isExpanded ? 'Expand' : 'Collapse';
});
// Store complex data (serialize as JSON)
const config = { theme: 'dark', lang: 'en', notifications: true };
element.dataset.config = JSON.stringify(config);
// Retrieve complex data
const retrievedConfig = JSON.parse(element.dataset.config);
// Form input attributes
const emailInput = document.querySelector('input[type="email"]');
emailInput.value = 'user@example.com';
emailInput.placeholder = 'Enter your email';
emailInput.required = true;
emailInput.pattern = '[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$';
// Custom validation
emailInput.setCustomValidity('Please enter a company email');
emailInput.setCustomValidity(''); // Clear custom validation
// Get/set multiple attributes efficiently
function setAttributes(element, attrs) {
Object.keys(attrs).forEach(key => {
element.setAttribute(key, attrs[key]);
});
}
setAttributes(link, {
href: 'https://example.com',
target: '_blank',
rel: 'noopener noreferrer'
});
Attach event handlers efficiently and use event delegation for dynamic elements.
const button = document.querySelector('#myButton');
// Add event listener
button.addEventListener('click', (event) => {
console.log('Button clicked!');
console.log('Event:', event);
console.log('Target:', event.target);
});
// Multiple handlers for same event
button.addEventListener('click', handler1);
button.addEventListener('click', handler2);
// Remove event listener (need reference to same function)
function handleClick(e) {
console.log('Clicked');
}
button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);
// Event options
button.addEventListener('click', handler, {
once: true, // Remove after first call
passive: true, // Won't call preventDefault
capture: false // Bubble phase (default)
});
// Prevent default behavior
const link = document.querySelector('a');
link.addEventListener('click', (e) => {
e.preventDefault(); // Prevent navigation
console.log('Link clicked but not followed');
});
// Stop propagation
const parent = document.querySelector('.parent');
const child = document.querySelector('.child');
parent.addEventListener('click', () => console.log('Parent clicked'));
child.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent bubble to parent
console.log('Child clicked');
});
// Event delegation (efficient for dynamic elements)
const list = document.querySelector('#todoList');
// BAD: Adding listener to each item
// document.querySelectorAll('.todo-item').forEach(item => {
// item.addEventListener('click', handleItemClick);
// });
// GOOD: Single listener on parent
list.addEventListener('click', (e) => {
const item = e.target.closest('.todo-item');
if (item) {
console.log('Todo clicked:', item.dataset.id);
// Handle different clicked elements
if (e.target.matches('.delete-btn')) {
deleteTodo(item);
} else if (e.target.matches('.edit-btn')) {
editTodo(item);
} else if (e.target.matches('.checkbox')) {
toggleTodo(item);
}
}
});
function deleteTodo(item) {
item.remove();
}
function editTodo(item) {
const text = item.querySelector('.todo-text');
text.contentEditable = true;
text.focus();
}
function toggleTodo(item) {
item.classList.toggle('completed');
}
// Common events
const input = document.querySelector('input');
input.addEventListener('input', (e) => {
console.log('Value:', e.target.value); // On every keystroke
});
input.addEventListener('change', (e) => {
console.log('Changed to:', e.target.value); // On blur
});
input.addEventListener('focus', () => console.log('Input focused'));
input.addEventListener('blur', () => console.log('Input blurred'));
// Form events
const form = document.querySelector('form');
form.addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log('Form data:', data);
});
// Keyboard events
document.addEventListener('keydown', (e) => {
console.log('Key:', e.key);
console.log('Code:', e.code);
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
saveDocument();
}
});
// Mouse events
element.addEventListener('mouseenter', () => console.log('Mouse entered'));
element.addEventListener('mouseleave', () => console.log('Mouse left'));
element.addEventListener('mousemove', (e) => {
console.log('Mouse position:', e.clientX, e.clientY);
});
Navigate the DOM tree to find parent, child, and sibling elements.
const element = document.querySelector('.current');
// Parent traversal
const parent = element.parentElement;
const parentNode = element.parentNode; // Can be non-element nodes
// Find nearest ancestor matching selector
const container = element.closest('.container');
const form = element.closest('form');
// Children
const children = element.children; // HTMLCollection of element children
const firstChild = element.firstElementChild;
const lastChild = element.lastElementChild;
// All child nodes (including text nodes)
const childNodes = element.childNodes; // NodeList
// Siblings
const nextSibling = element.nextElementSibling;
const prevSibling = element.previousElementSibling;
// All siblings
function getSiblings(element) {
return [...element.parentElement.children].filter(child => child !== element);
}
const siblings = getSiblings(element);
// Recursive traversal
function walkDOM(node, callback) {
callback(node);
node = node.firstChild;
while (node) {
walkDOM(node, callback);
node = node.nextSibling;
}
}
walkDOM(document.body, (node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
console.log(node.tagName);
}
});
// Find all elements of type
function findElementsByType(root, tagName) {
const elements = [];
function traverse(node) {
if (node.tagName === tagName.toUpperCase()) {
elements.push(node);
}
[...node.children].forEach(traverse);
}
traverse(root);
return elements;
}
const allDivs = findElementsByType(document.body, 'div');
// Practical: Build breadcrumb trail
function getBreadcrumb(element) {
const breadcrumb = [];
let current = element;
while (current && current !== document.body) {
breadcrumb.unshift({
tag: current.tagName,
id: current.id,
class: current.className
});
current = current.parentElement;
}
return breadcrumb;
}
const trail = getBreadcrumb(document.querySelector('.deep-nested'));
console.log(trail);
// Check if element contains another
const parent = document.querySelector('.parent');
const child = document.querySelector('.child');
if (parent.contains(child)) {
console.log('Parent contains child');
}
// Get element index among siblings
function getElementIndex(element) {
return [...element.parentElement.children].indexOf(element);
}
const index = getElementIndex(element);
console.log('Element is at index:', index);
Real-world patterns and best practices for DOM manipulation.
// Safe DOM ready
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(() => {
console.log('DOM is ready');
initializeApp();
});
// Component pattern
class TodoList {
constructor(container) {
this.container = container;
this.items = [];
this.render();
this.attachEvents();
}
render() {
this.container.innerHTML = `
<div class="todo-list">
<input type="text" class="todo-input" placeholder="Add todo...">
<button class="todo-add">Add</button>
<ul class="todo-items"></ul>
</div>
`;
this.input = this.container.querySelector('.todo-input');
this.itemsList = this.container.querySelector('.todo-items');
}
attachEvents() {
this.container.querySelector('.todo-add').addEventListener('click', () => {
this.addItem(this.input.value);
this.input.value = '';
});
this.itemsList.addEventListener('click', (e) => {
if (e.target.matches('.delete')) {
const id = e.target.closest('li').dataset.id;
this.removeItem(id);
}
});
}
addItem(text) {
if (!text.trim()) return;
const id = Date.now().toString();
this.items.push({ id, text });
const li = document.createElement('li');
li.dataset.id = id;
li.innerHTML = `
<span>${text}</span>
<button class="delete">Delete</button>
`;
this.itemsList.appendChild(li);
}
removeItem(id) {
this.items = this.items.filter(item => item.id !== id);
this.itemsList.querySelector(`[data-id="${id}"]`).remove();
}
}
// Usage
const todoList = new TodoList(document.querySelector('#app'));
// Batch DOM updates
function batchUpdate(updates) {
// Force synchronous layout
const container = document.querySelector('.container');
// Detach from DOM (prevents reflows)
const parent = container.parentElement;
const nextSibling = container.nextElementSibling;
parent.removeChild(container);
// Make all updates
updates.forEach(update => update());
// Reattach
parent.insertBefore(container, nextSibling);
}
// Virtual DOM-like diffing (simplified)
function updateList(container, newItems, oldItems) {
const itemsToAdd = newItems.filter(item =>
!oldItems.find(old => old.id === item.id)
);
const itemsToRemove = oldItems.filter(item =>
!newItems.find(newItem => newItem.id === item.id)
);
// Remove
itemsToRemove.forEach(item => {
container.querySelector(`[data-id="${item.id}"]`)?.remove();
});
// Add
itemsToAdd.forEach(item => {
const el = document.createElement('div');
el.dataset.id = item.id;
el.textContent = item.text;
container.appendChild(el);
});
}
Understanding JavaScript's asynchronous execution model
The call stack tracks function execution in Last-In-First-Out (LIFO) order. Understanding stack behavior is key to debugging.
// Stack execution order
function first() {
console.log('First function');
second();
console.log('First function done');
}
function second() {
console.log('Second function');
third();
console.log('Second function done');
}
function third() {
console.log('Third function');
}
first();
// Output:
// First function
// Second function
// Third function
// Second function done
// First function done
// Stack overflow example
function recursiveWithoutBase() {
recursiveWithoutBase(); // No base case!
}
// This will crash: Maximum call stack size exceeded
// recursiveWithoutBase();
// Proper recursion with base case
function countdown(n) {
if (n <= 0) return; // Base case
console.log(n);
countdown(n - 1);
}
countdown(5);
// Stack trace visualization
function a() {
console.trace('Stack trace from a()');
b();
}
function b() {
console.trace('Stack trace from b()');
c();
}
function c() {
console.trace('Stack trace from c()');
}
a(); // Shows full stack trace at each level
The event loop continuously checks the call stack and processes tasks from various queues in specific phases.
// Event loop visualization
console.log('1. Synchronous');
setTimeout(() => {
console.log('2. Macrotask (timer)');
}, 0);
Promise.resolve().then(() => {
console.log('3. Microtask (promise)');
});
console.log('4. Synchronous');
// Output order:
// 1. Synchronous
// 4. Synchronous
// 3. Microtask (promise)
// 2. Macrotask (timer)
// Detailed execution phases
console.log('Start'); // Call stack
setTimeout(() => {
console.log('Timeout 1'); // Macrotask queue
Promise.resolve().then(() => console.log('Promise in timeout'));
}, 0);
setTimeout(() => {
console.log('Timeout 2'); // Macrotask queue
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1'); // Microtask queue
return Promise.resolve();
})
.then(() => console.log('Promise 2')); // Microtask queue
console.log('End'); // Call stack
// Output:
// Start
// End
// Promise 1
// Promise 2
// Timeout 1
// Promise in timeout
// Timeout 2
// Event loop never blocks
function longRunningTask() {
const start = Date.now();
while (Date.now() - start < 3000) {
// Blocks for 3 seconds - BAD!
}
console.log('Task done');
}
// This blocks the entire thread
// longRunningTask();
// Better: Break into chunks
function chunkTask(items, chunkSize = 100) {
let index = 0;
function processChunk() {
const end = Math.min(index + chunkSize, items.length);
for (let i = index; i < end; i++) {
// Process items[i]
console.log(`Processing item ${i}`);
}
index = end;
if (index < items.length) {
setTimeout(processChunk, 0); // Let event loop breathe
}
}
processChunk();
}
// Usage
chunkTask(Array.from({ length: 1000 }, (_, i) => i));
The callback/task queue holds macrotasks like setTimeout, setInterval, and I/O operations.
// Macrotasks are processed after microtasks
console.log('Script start');
setTimeout(() => {
console.log('setTimeout 0');
}, 0);
setTimeout(() => {
console.log('setTimeout 10');
}, 10);
setInterval(() => {
console.log('setInterval - fires repeatedly');
}, 1000);
// I/O operations are also macrotasks
fetch('https://api.example.com/data')
.then(() => console.log('Fetch complete'));
console.log('Script end');
// Macrotask scheduling
const tasks = [];
function scheduleMacrotask(fn) {
setTimeout(fn, 0);
}
scheduleMacrotask(() => console.log('Macrotask 1'));
scheduleMacrotask(() => console.log('Macrotask 2'));
scheduleMacrotask(() => console.log('Macrotask 3'));
// Macrotasks don't block each other
setTimeout(() => {
console.log('First timeout starts');
// Even with blocking code, other timeouts wait
const start = Date.now();
while (Date.now() - start < 2000) {}
console.log('First timeout ends');
}, 0);
setTimeout(() => {
console.log('Second timeout executes after first completes');
}, 0);
// Event listeners add to callback queue
document.querySelector('#myButton')?.addEventListener('click', () => {
console.log('Click handler - macrotask');
Promise.resolve().then(() => {
console.log('Microtask inside click handler');
});
console.log('Click handler continues');
});
// Output when clicked:
// Click handler - macrotask
// Click handler continues
// Microtask inside click handler
Microtasks (promises, queueMicrotask) have higher priority and run before the next macrotask.
// Microtasks always run before macrotasks
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve()
.then(() => console.log('Promise 1'))
.then(() => console.log('Promise 2'))
.then(() => console.log('Promise 3'));
queueMicrotask(() => console.log('queueMicrotask'));
console.log('Synchronous');
// Output:
// Synchronous
// Promise 1
// queueMicrotask
// Promise 2
// Promise 3
// Timeout
// Microtask chain doesn't block macrotasks
Promise.resolve()
.then(() => {
console.log('Microtask 1');
return Promise.resolve();
})
.then(() => {
console.log('Microtask 2');
setTimeout(() => console.log('Timeout in microtask'), 0);
})
.then(() => console.log('Microtask 3'));
setTimeout(() => console.log('Macrotask'), 0);
// Microtask infinite loop caution
let count = 0;
function scheduleRecursiveMicrotask() {
queueMicrotask(() => {
console.log(`Microtask ${++count}`);
if (count < 1000000) {
scheduleRecursiveMicrotask(); // This BLOCKS macrotasks!
}
});
}
// DON'T DO THIS - starves macrotasks
// scheduleRecursiveMicrotask();
// Better: Give macrotasks a chance
function scheduleWithBreaks() {
let count = 0;
function schedule() {
if (count < 100) {
queueMicrotask(() => {
console.log(`Task ${++count}`);
});
setTimeout(schedule, 0); // Let macrotasks run
}
}
schedule();
}
// Async/await creates microtasks
async function asyncFunction() {
console.log('Async start');
await Promise.resolve(); // Suspends, creates microtask
console.log('After await'); // Runs as microtask
}
console.log('Before async');
asyncFunction();
console.log('After async call');
// Output:
// Before async
// Async start
// After async call
// After await
Predict and understand complex execution order by tracking synchronous, microtask, and macrotask code.
// Complex example
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => console.log('3'));
}, 0);
Promise.resolve()
.then(() => {
console.log('4');
setTimeout(() => console.log('5'), 0);
})
.then(() => console.log('6'));
console.log('7');
// Output: 1, 7, 4, 6, 2, 3, 5
// Step-by-step breakdown:
// Initial (sync): 1, 7
// Microtasks: 4, 6
// Macrotasks: 2 (triggers microtask 3), then 5
// Nested promises and timeouts
setTimeout(() => {
console.log('Timeout 1');
Promise.resolve()
.then(() => console.log('Promise 1'))
.then(() => console.log('Promise 2'));
setTimeout(() => console.log('Timeout 2'), 0);
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 3');
setTimeout(() => console.log('Timeout 3'), 0);
});
// Real-world: Button click simulation
function simulateComplexEvent() {
console.log('Event triggered');
// Sync work
const data = processData();
console.log('Data processed');
// Async validation (microtask)
Promise.resolve()
.then(() => {
console.log('Validation started');
return validateData(data);
})
.then(isValid => {
console.log('Validation complete:', isValid);
if (isValid) {
// API call (macrotask)
setTimeout(() => {
console.log('API call made');
}, 0);
}
});
console.log('Event handler done');
}
function processData() {
return { value: 42 };
}
function validateData(data) {
return data.value > 0;
}
// Debugging execution order
function debugEventLoop() {
const log = [];
log.push('Start');
setTimeout(() => log.push('Timeout 1'), 0);
Promise.resolve().then(() => {
log.push('Promise 1');
return Promise.resolve();
}).then(() => log.push('Promise 2'));
setTimeout(() => {
log.push('Timeout 2');
console.log('Execution order:', log);
}, 10);
log.push('End');
}
debugEventLoop();
Identify and fix blocking operations that freeze the UI and degrade user experience.
// BAD: Blocking operation
function heavyCalculation() {
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i;
}
return result;
}
// This freezes the UI for seconds
// const result = heavyCalculation();
// GOOD: Break into chunks with setTimeout
function heavyCalculationNonBlocking(callback) {
let result = 0;
let i = 0;
const chunkSize = 10000000;
function processChunk() {
const end = Math.min(i + chunkSize, 1000000000);
for (; i < end; i++) {
result += i;
}
if (i < 1000000000) {
setTimeout(processChunk, 0); // Yield to event loop
} else {
callback(result);
}
}
processChunk();
}
heavyCalculationNonBlocking((result) => {
console.log('Result:', result);
});
// BETTER: Use Web Workers (covered next)
// Detect long-running tasks
let lastTime = performance.now();
setInterval(() => {
const now = performance.now();
const delta = now - lastTime;
if (delta > 100) {
console.warn(`Long task detected: ${delta}ms`);
}
lastTime = now;
}, 50);
// requestIdleCallback for non-critical work
function deferredWork() {
if ('requestIdleCallback' in window) {
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0) {
// Do non-critical work
console.log('Processing during idle time');
}
});
} else {
// Fallback
setTimeout(deferredWork, 1);
}
}
// Yield to browser periodically
async function processLargeArray(items) {
for (let i = 0; i < items.length; i++) {
processItem(items[i]);
// Yield every 100 items
if (i % 100 === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
}
function processItem(item) {
// Heavy processing
console.log('Processing:', item);
}
Offload heavy computations to background threads with Web Workers to keep the UI responsive.
// Main thread (main.js)
const worker = new Worker('worker.js');
// Send data to worker
worker.postMessage({ type: 'calculate', data: [1, 2, 3, 4, 5] });
// Receive results from worker
worker.addEventListener('message', (e) => {
console.log('Result from worker:', e.data);
if (e.data.type === 'result') {
displayResult(e.data.value);
}
});
// Handle errors
worker.addEventListener('error', (e) => {
console.error('Worker error:', e.message);
});
// Terminate worker when done
function cleanup() {
worker.terminate();
}
// Worker thread (worker.js)
// This code would be in a separate file
/*
self.addEventListener('message', (e) => {
if (e.data.type === 'calculate') {
const result = heavyCalculation(e.data.data);
self.postMessage({
type: 'result',
value: result
});
}
});
function heavyCalculation(data) {
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += data[i % data.length];
}
return sum;
}
*/
// Inline worker using Blob
function createInlineWorker(fn) {
const blob = new Blob([`(${fn.toString()})()`], {
type: 'application/javascript'
});
return new Worker(URL.createObjectURL(blob));
}
const inlineWorker = createInlineWorker(() => {
self.addEventListener('message', (e) => {
const result = e.data.reduce((a, b) => a + b, 0);
self.postMessage(result);
});
});
inlineWorker.postMessage([1, 2, 3, 4, 5]);
inlineWorker.addEventListener('message', (e) => {
console.log('Sum:', e.data);
});
// Worker pool for multiple concurrent tasks
class WorkerPool {
constructor(workerScript, size = 4) {
this.workers = [];
this.queue = [];
for (let i = 0; i < size; i++) {
const worker = new Worker(workerScript);
worker.busy = false;
worker.addEventListener('message', (e) => {
worker.busy = false;
worker.callback(e.data);
this.processQueue();
});
this.workers.push(worker);
}
}
execute(data) {
return new Promise((resolve) => {
this.queue.push({ data, callback: resolve });
this.processQueue();
});
}
processQueue() {
if (this.queue.length === 0) return;
const availableWorker = this.workers.find(w => !w.busy);
if (!availableWorker) return;
const task = this.queue.shift();
availableWorker.busy = true;
availableWorker.callback = task.callback;
availableWorker.postMessage(task.data);
}
terminate() {
this.workers.forEach(w => w.terminate());
}
}
// Usage
const pool = new WorkerPool('heavy-task-worker.js', 4);
Promise.all([
pool.execute({ input: 1 }),
pool.execute({ input: 2 }),
pool.execute({ input: 3 })
]).then(results => {
console.log('All tasks complete:', results);
});
Tools and techniques for diagnosing and fixing event loop related problems.
// Visualize execution order
function logWithPhase(message, phase) {
const phases = {
sync: '🟢',
micro: '🔵',
macro: '🔴'
};
console.log(`${phases[phase]} [${phase.toUpperCase()}] ${message}`);
}
logWithPhase('Start', 'sync');
setTimeout(() => {
logWithPhase('Timeout', 'macro');
}, 0);
Promise.resolve().then(() => {
logWithPhase('Promise', 'micro');
});
logWithPhase('End', 'sync');
// Performance monitoring
class EventLoopMonitor {
constructor() {
this.longTasks = [];
this.lastCheck = performance.now();
this.start();
}
start() {
this.checkInterval = setInterval(() => {
const now = performance.now();
const gap = now - this.lastCheck;
// Expected: ~10ms, if > 50ms, something blocked
if (gap > 50) {
this.longTasks.push({
duration: gap,
timestamp: now
});
console.warn(`Long task: ${gap.toFixed(2)}ms`);
}
this.lastCheck = now;
}, 10);
}
getReport() {
return {
totalLongTasks: this.longTasks.length,
averageDuration: this.longTasks.reduce((a, b) => a + b.duration, 0) / this.longTasks.length,
longestTask: Math.max(...this.longTasks.map(t => t.duration))
};
}
stop() {
clearInterval(this.checkInterval);
}
}
const monitor = new EventLoopMonitor();
// Simulate work and check report later
setTimeout(() => {
console.log('Report:', monitor.getReport());
monitor.stop();
}, 5000);
// Trace async operations
function traceAsync(name, fn) {
console.log(`[${name}] Starting`);
const result = fn();
if (result && typeof result.then === 'function') {
return result.then(
(value) => {
console.log(`[${name}] Resolved:`, value);
return value;
},
(error) => {
console.error(`[${name}] Rejected:`, error);
throw error;
}
);
}
console.log(`[${name}] Completed:`, result);
return result;
}
// Usage
traceAsync('Fetch Users', () => {
return fetch('/api/users').then(r => r.json());
});
Master timing functions and asynchronous execution patterns
JavaScript provides timer functions to delay execution or repeat tasks at intervals.
// setTimeout - execute once after delay
setTimeout(() => {
console.log('Executed after 2 seconds');
}, 2000);
// setTimeout with parameters
setTimeout((name, age) => {
console.log(`${name} is ${age} years old`);
}, 1000, 'John', 30);
// setInterval - execute repeatedly
const intervalId = setInterval(() => {
console.log('This runs every second');
}, 1000);
// Clear interval after 5 seconds
setTimeout(() => {
clearInterval(intervalId);
console.log('Interval cleared');
}, 5000);
// Return timeout ID for clearing
const timeoutId = setTimeout(() => {
console.log('This might not run');
}, 3000);
// Clear before it executes
clearTimeout(timeoutId);
// Countdown timer example
let count = 10;
const countdown = setInterval(() => {
console.log(count);
count--;
if (count < 0) {
clearInterval(countdown);
console.log('Blast off!');
}
}, 1000);
Debounce delays function execution until after a pause in events, perfect for search inputs and resize handlers.
// Basic debounce
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Usage: Search as user types
const searchAPI = (query) => {
console.log('Searching for:', query);
// Make API call here
};
const debouncedSearch = debounce(searchAPI, 500);
// Only searches 500ms after user stops typing
document.querySelector('#search').addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
// Advanced debounce with immediate option
function debounceAdvanced(func, delay, immediate = false) {
let timeoutId;
return function(...args) {
const callNow = immediate && !timeoutId;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
timeoutId = null;
if (!immediate) {
func.apply(this, args);
}
}, delay);
if (callNow) {
func.apply(this, args);
}
};
}
// Execute immediately, then debounce subsequent calls
const saveData = debounceAdvanced((data) => {
console.log('Saving:', data);
}, 1000, true);
// Real-world: Window resize handler
const handleResize = debounce(() => {
console.log('Window resized to:', window.innerWidth);
// Recalculate layouts, update responsive components
}, 250);
window.addEventListener('resize', handleResize);
Throttle limits function execution to once per time period, ideal for scroll events and continuous actions.
// Basic throttle
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// Usage: Scroll event handler
const handleScroll = () => {
console.log('Scroll position:', window.scrollY);
// Update scroll-based animations, lazy load images
};
const throttledScroll = throttle(handleScroll, 200);
window.addEventListener('scroll', throttledScroll);
// Advanced throttle with trailing call
function throttleAdvanced(func, limit) {
let inThrottle;
let lastFunc;
let lastRan;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
lastRan = Date.now();
inThrottle = true;
setTimeout(() => {
inThrottle = false;
if (lastFunc) {
throttleAdvanced(func, limit).apply(this, args);
lastFunc = null;
}
}, limit);
} else {
lastFunc = args;
}
};
}
// Real-world: Button click prevention
const submitForm = throttle((formData) => {
console.log('Submitting form...');
// Prevent multiple rapid submissions
}, 2000);
document.querySelector('#submitBtn').addEventListener('click', () => {
submitForm({ name: 'John', email: 'john@example.com' });
});
// Comparison: Debounce vs Throttle
const input = document.querySelector('#input');
// Debounce: Wait for pause
input.addEventListener('input', debounce((e) => {
console.log('Debounced:', e.target.value);
}, 500));
// Throttle: Execute at regular intervals
input.addEventListener('input', throttle((e) => {
console.log('Throttled:', e.target.value);
}, 500));
RAF synchronizes with browser repaints for smooth animations at 60fps, more efficient than setInterval.
// Basic animation loop
function animate() {
// Update animation state
console.log('Frame rendered');
requestAnimationFrame(animate);
}
animate();
// Controlled animation with start/stop
class Animation {
constructor(callback) {
this.callback = callback;
this.rafId = null;
this.running = false;
}
start() {
if (this.running) return;
this.running = true;
const loop = (timestamp) => {
this.callback(timestamp);
if (this.running) {
this.rafId = requestAnimationFrame(loop);
}
};
this.rafId = requestAnimationFrame(loop);
}
stop() {
this.running = false;
if (this.rafId) {
cancelAnimationFrame(this.rafId);
}
}
}
// Usage: Smooth counter animation
const counter = document.querySelector('#counter');
let count = 0;
const target = 1000;
const duration = 2000;
let startTime = null;
function animateCounter(timestamp) {
if (!startTime) startTime = timestamp;
const progress = timestamp - startTime;
const percentage = Math.min(progress / duration, 1);
count = Math.floor(percentage * target);
counter.textContent = count;
if (percentage < 1) {
requestAnimationFrame(animateCounter);
}
}
requestAnimationFrame(animateCounter);
// Smooth scroll implementation
function smoothScrollTo(targetY, duration = 1000) {
const startY = window.scrollY;
const distance = targetY - startY;
let startTime = null;
function scroll(currentTime) {
if (!startTime) startTime = currentTime;
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing function
const ease = progress * (2 - progress); // easeOutQuad
window.scrollTo(0, startY + distance * ease);
if (progress < 1) {
requestAnimationFrame(scroll);
}
}
requestAnimationFrame(scroll);
}
// Usage
document.querySelector('#scrollBtn').addEventListener('click', () => {
smoothScrollTo(1000, 800);
});
Queue async tasks to control concurrency and ensure orderly execution.
// Simple task queue
class TaskQueue {
constructor() {
this.queue = [];
this.running = false;
}
async add(task) {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
}
});
this.process();
});
}
async process() {
if (this.running || this.queue.length === 0) return;
this.running = true;
const task = this.queue.shift();
await task();
this.running = false;
this.process();
}
}
// Usage
const queue = new TaskQueue();
queue.add(() => fetch('/api/user/1').then(r => r.json()));
queue.add(() => fetch('/api/user/2').then(r => r.json()));
queue.add(() => fetch('/api/user/3').then(r => r.json()));
// Concurrent task queue (limit parallel execution)
class ConcurrentQueue {
constructor(maxConcurrent = 2) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
}
async add(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
this.process();
});
}
async process() {
while (this.running < this.maxConcurrent && this.queue.length > 0) {
const { task, resolve, reject } = this.queue.shift();
this.running++;
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process();
}
}
}
}
// Usage: Limit to 3 concurrent API calls
const apiQueue = new ConcurrentQueue(3);
const promises = Array.from({ length: 10 }, (_, i) =>
apiQueue.add(() => fetch(`/api/item/${i}`).then(r => r.json()))
);
const results = await Promise.all(promises);
Schedule tasks to run at specific times or intervals with advanced control.
// Delayed execution with promise
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Usage with async/await
async function sequence() {
console.log('Start');
await delay(1000);
console.log('After 1 second');
await delay(2000);
console.log('After 3 seconds total');
}
// Retry with delay
async function retryWithDelay(fn, retries = 3, delayMs = 1000) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
console.log(`Retry ${i + 1} after ${delayMs}ms`);
await delay(delayMs);
}
}
}
// Timeout promise
function timeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
)
]);
}
// Usage
try {
const data = await timeout(
fetch('https://api.example.com/slow'),
5000
);
} catch (error) {
console.error('Request timed out');
}
// Scheduled task runner
class Scheduler {
constructor() {
this.tasks = [];
}
schedule(task, delay) {
const id = setTimeout(() => {
task();
this.tasks = this.tasks.filter(t => t.id !== id);
}, delay);
this.tasks.push({ id, task, delay });
return id;
}
repeat(task, interval) {
const id = setInterval(task, interval);
this.tasks.push({ id, task, interval, repeating: true });
return id;
}
cancel(id) {
clearTimeout(id);
clearInterval(id);
this.tasks = this.tasks.filter(t => t.id !== id);
}
cancelAll() {
this.tasks.forEach(({ id }) => {
clearTimeout(id);
clearInterval(id);
});
this.tasks = [];
}
}
// Usage
const scheduler = new Scheduler();
scheduler.schedule(() => console.log('One time task'), 1000);
const repeatId = scheduler.repeat(() => console.log('Repeating'), 2000);
// Cancel after 10 seconds
setTimeout(() => scheduler.cancel(repeatId), 10000);
Measure execution time and optimize performance-critical code paths.
// Basic timing with console.time
console.time('operation');
// ... code to measure
console.timeEnd('operation'); // Logs: operation: 123.456ms
// Performance API
const start = performance.now();
// ... code to measure
const end = performance.now();
console.log(`Execution took ${end - start}ms`);
// Function timing wrapper
function measureTime(fn, label = 'Function') {
return function(...args) {
const start = performance.now();
const result = fn.apply(this, args);
const end = performance.now();
console.log(`${label} took ${end - start}ms`);
return result;
};
}
// Async function timing
async function measureAsync(fn, label = 'Async Function') {
const start = performance.now();
try {
const result = await fn();
const end = performance.now();
console.log(`${label} took ${end - start}ms`);
return result;
} catch (error) {
const end = performance.now();
console.log(`${label} failed after ${end - start}ms`);
throw error;
}
}
// Performance marks and measures
performance.mark('start-fetch');
await fetch('https://api.example.com/data');
performance.mark('end-fetch');
performance.measure('fetch-duration', 'start-fetch', 'end-fetch');
const measures = performance.getEntriesByType('measure');
console.log(measures[0].duration);
// Benchmark utility
class Benchmark {
constructor(iterations = 1000) {
this.iterations = iterations;
}
run(fn, name = 'Test') {
const times = [];
for (let i = 0; i < this.iterations; i++) {
const start = performance.now();
fn();
const end = performance.now();
times.push(end - start);
}
const avg = times.reduce((a, b) => a + b) / times.length;
const min = Math.min(...times);
const max = Math.max(...times);
console.log(`${name} - Avg: ${avg.toFixed(3)}ms, Min: ${min.toFixed(3)}ms, Max: ${max.toFixed(3)}ms`);
}
}
// Usage
const bench = new Benchmark(10000);
bench.run(() => Array.from({ length: 100 }, (_, i) => i * 2), 'Array.from');
bench.run(() => [...Array(100)].map((_, i) => i * 2), 'Spread operator');
Combine timing patterns for production-ready solutions.
// Auto-save with debounce
class AutoSave {
constructor(saveFn, delay = 2000) {
this.saveFn = saveFn;
this.delay = delay;
this.saveDebounced = debounce(this.save.bind(this), delay);
this.isDirty = false;
}
onChange(data) {
this.isDirty = true;
this.saveDebounced(data);
}
async save(data) {
if (!this.isDirty) return;
try {
await this.saveFn(data);
this.isDirty = false;
console.log('Saved successfully');
} catch (error) {
console.error('Save failed:', error);
}
}
}
// Usage
const autoSave = new AutoSave(
(data) => fetch('/api/save', {
method: 'POST',
body: JSON.stringify(data)
})
);
document.querySelector('#editor').addEventListener('input', (e) => {
autoSave.onChange({ content: e.target.value });
});
// Infinite scroll with throttle
class InfiniteScroll {
constructor(loadMoreFn) {
this.loadMoreFn = loadMoreFn;
this.loading = false;
this.hasMore = true;
this.handleScroll = throttle(this.checkScroll.bind(this), 200);
window.addEventListener('scroll', this.handleScroll);
}
checkScroll() {
if (this.loading || !this.hasMore) return;
const scrollPosition = window.scrollY + window.innerHeight;
const threshold = document.documentElement.scrollHeight - 200;
if (scrollPosition >= threshold) {
this.loadMore();
}
}
async loadMore() {
this.loading = true;
try {
const items = await this.loadMoreFn();
this.hasMore = items.length > 0;
} catch (error) {
console.error('Failed to load more:', error);
} finally {
this.loading = false;
}
}
destroy() {
window.removeEventListener('scroll', this.handleScroll);
}
}
// Usage
const infiniteScroll = new InfiniteScroll(async () => {
const response = await fetch('/api/items?page=' + currentPage);
const items = await response.json();
currentPage++;
return items;
});
Coordinate asynchronous work with promises, chaining, and async functions.
Promises represent future values. They transition from pending to fulfilled or rejected. Combine .then/.catch for chaining, use utilities like Promise.all, and switch to async/await for linear-style code while still returning promises.
Wrap asynchronous work in the Promise constructor or return promises from APIs.
function delay(ms) {
return new Promise((resolve) => {
setTimeout(() => resolve(`Done in ${ms}ms`), ms);
});
}
delay(500).then(console.log);
function fetchUser(id) {
return new Promise((resolve, reject) => {
if (!id) return reject(new Error('Missing id'));
resolve({ id, name: 'Nova' });
});
}
fetchUser(null).catch(err => console.error(err.message));
Return values propagate through chains; returning a promise waits for it.
delay(200)
.then(msg => {
console.log(msg);
return fetchUser(1);
})
.then(user => console.log(user.name))
.catch(err => console.error('Error', err))
.finally(() => console.log('Done'));
Run tasks concurrently or race them using built-in combinators.
const a = delay(100);
const b = delay(200);
const c = delay(300);
Promise.all([a, b, c])
.then(results => console.log(results))
.catch(err => console.error(err));
Promise.race([delay(1000), delay(50)]).then(console.log);
Promise.allSettled([
Promise.resolve('ok'),
Promise.reject('fail')
]).then(console.log);
async functions return promises. await pauses until fulfillment inside an async function.
async function loadProfile() {
try {
const user = await fetchUser(2);
const message = await delay(100);
return { user, message };
} catch (err) {
console.error(err);
return null;
}
}
loadProfile().then(console.log);
Use .catch or try/catch within async functions to handle rejections.
async function withRetry(fn, attempts = 3) {
let lastError;
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
lastError = err;
}
}
throw lastError;
}
withRetry(() => fetchUser(null)).catch(err => console.error('Failed', err.message));
Kick off promises before awaiting to run tasks in parallel.
async function loadDashboard() {
const userPromise = fetchUser(3);
const dataPromise = delay(120);
const [user, message] = await Promise.all([userPromise, dataPromise]);
return { user, message };
}
loadDashboard().then(console.log);
Promises and async/await flatten nested callbacks, improving readability.
// Callback style
// fetch(url, res => {
// parse(res, parsed => {
// save(parsed, () => console.log('done'));
// });
// });
// Promise style
fetch(url)
.then(parse)
.then(save)
.then(() => console.log('done'));
// Async style
async function run() {
const res = await fetch(url);
const parsed = await parse(res);
await save(parsed);
console.log('done');
}
Promise callbacks run as microtasks after the current call stack, before timers.
console.log('start');
Promise.resolve('p').then(console.log);
setTimeout(() => console.log('timeout'), 0);
console.log('end');
// order: start, end, p, timeout
Promises themselves cannot be canceled, but you can design APIs that support aborting.
const controller = new AbortController();
const { signal } = controller;
fetch('/api/data', { signal })
.then(res => res.json())
.then(console.log)
.catch(err => {
if (err.name === 'AbortError') console.log('Request canceled');
});
setTimeout(() => controller.abort(), 50);
Always attach .catch or wrap awaits in try/catch to prevent unhandled rejection warnings.
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled', event.reason);
});
Promise.reject(new Error('boom')); // caught by listener
setTimeout in a promise and await it to simulate delays..catch.Promise.all to fetch three resources simultaneously and handle a rejection.Promise.race and Promise.allSettled on mixed success/failure promises.finally to clean up UI state after a promise chain.Promise.resolve with setTimeout..then/.catch/.finally to compose async flows; return promises to control sequencing.Promise.all awaits all, Promise.race resolves on the first, and Promise.allSettled collects results regardless of outcome.async/await offers synchronous-looking code while still leveraging promises and microtasks.Explore Fetch and APIs to apply promises to real network calls, or revisit Classes for structuring asynchronous service layers.
Master modern HTTP communication with the Fetch API
The fetch() function returns a promise that resolves to a Response object. Basic syntax requires just a URL.
// Simple GET request
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
// Using async/await (preferred)
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
// Check response status
async function fetchWithStatusCheck() {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
GET retrieves data, while POST sends data to the server. Configure requests using the options object.
// GET request with query parameters
async function getUsers(page = 1) {
const url = new URL('https://api.example.com/users');
url.searchParams.append('page', page);
url.searchParams.append('limit', 10);
const response = await fetch(url);
return await response.json();
}
// POST request with JSON body
async function createUser(userData) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`Failed to create user: ${response.status}`);
}
return await response.json();
}
// PUT request to update data
async function updateUser(id, updates) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
return await response.json();
}
// DELETE request
async function deleteUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'DELETE'
});
return response.ok;
}
Headers provide metadata about requests. Body formats include JSON, FormData, and plain text.
// Custom headers
async function fetchWithAuth(token) {
const response = await fetch('https://api.example.com/protected', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
'X-Custom-Header': 'value'
}
});
return await response.json();
}
// FormData for file uploads
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'My file');
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData // Don't set Content-Type, browser sets it
});
return await response.json();
}
// Multiple files with FormData
async function uploadMultipleFiles(files) {
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('files[]', files[i]);
}
const response = await fetch('https://api.example.com/upload-multiple', {
method: 'POST',
body: formData
});
return await response.json();
}
// URLSearchParams for form-encoded data
async function submitForm(data) {
const params = new URLSearchParams();
params.append('username', data.username);
params.append('password', data.password);
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
return await response.json();
}
Handle different response types and HTTP status codes appropriately for robust error handling.
// Different response types
async function handleDifferentResponses(url) {
const response = await fetch(url);
const contentType = response.headers.get('content-type');
if (contentType.includes('application/json')) {
return await response.json();
} else if (contentType.includes('text/html')) {
return await response.text();
} else if (contentType.includes('image')) {
return await response.blob();
} else {
return await response.arrayBuffer();
}
}
// Handle specific status codes
async function fetchWithStatusHandling(url) {
const response = await fetch(url);
switch (response.status) {
case 200:
return await response.json();
case 201:
console.log('Resource created successfully');
return await response.json();
case 204:
return null; // No content
case 400:
throw new Error('Bad request - check your data');
case 401:
throw new Error('Unauthorized - login required');
case 403:
throw new Error('Forbidden - insufficient permissions');
case 404:
throw new Error('Resource not found');
case 500:
throw new Error('Server error - try again later');
default:
throw new Error(`Unexpected status: ${response.status}`);
}
}
// Parse error responses
async function handleErrorResponse(response) {
if (!response.ok) {
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
try {
const errorData = await response.json();
errorMessage = errorData.message || errorMessage;
} catch (e) {
// Response wasn't JSON
}
throw new Error(errorMessage);
}
return response;
}
Implement robust error handling with retry mechanisms for network failures and timeouts.
// Retry logic with exponential backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
lastError = error;
console.log(`Attempt ${i + 1} failed:`, error.message);
if (i < maxRetries - 1) {
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
}
// Timeout wrapper
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
// Combined: retry with timeout
async function robustFetch(url, options = {}) {
return fetchWithRetry(
url,
options,
3
).catch(error => {
console.error('All retry attempts failed:', error);
throw error;
});
}
Use AbortController to cancel ongoing requests, preventing memory leaks and unnecessary network usage.
// Basic abort
const controller = new AbortController();
fetch('https://api.example.com/data', {
signal: controller.signal
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request was cancelled');
}
});
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
// Cancellable search with debounce
class SearchManager {
constructor() {
this.controller = null;
}
async search(query) {
// Cancel previous request
if (this.controller) {
this.controller.abort();
}
this.controller = new AbortController();
try {
const response = await fetch(
`https://api.example.com/search?q=${query}`,
{ signal: this.controller.signal }
);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Search cancelled');
return null;
}
throw error;
}
}
}
const searchManager = new SearchManager();
// Use in search input
document.querySelector('#searchInput').addEventListener('input', (e) => {
searchManager.search(e.target.value)
.then(results => {
if (results) {
displayResults(results);
}
});
});
Handle Cross-Origin Resource Sharing and implement various authentication methods.
// CORS with credentials
async function fetchWithCredentials(url) {
const response = await fetch(url, {
credentials: 'include', // Send cookies
mode: 'cors' // Explicit CORS mode
});
return await response.json();
}
// Bearer token authentication
class ApiClient {
constructor(baseURL, token) {
this.baseURL = baseURL;
this.token = token;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
...options.headers
};
const response = await fetch(url, {
...options,
headers
});
if (response.status === 401) {
// Token expired, refresh or redirect to login
throw new Error('Authentication required');
}
return await response.json();
}
get(endpoint) {
return this.request(endpoint);
}
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
}
// Usage
const api = new ApiClient('https://api.example.com', 'your-token-here');
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John' });
// API key authentication
async function fetchWithApiKey(url, apiKey) {
const response = await fetch(url, {
headers: {
'X-API-Key': apiKey
}
});
return await response.json();
}
Common patterns for production applications including caching, batching, and request queuing.
// Simple cache wrapper
class CachedFetch {
constructor(ttl = 60000) { // 1 minute default
this.cache = new Map();
this.ttl = ttl;
}
async fetch(url, options = {}) {
const cacheKey = url + JSON.stringify(options);
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const response = await fetch(url, options);
const data = await response.json();
this.cache.set(cacheKey, {
data,
timestamp: Date.now()
});
return data;
}
clear() {
this.cache.clear();
}
}
// Batch requests
class RequestBatcher {
constructor(batchFn, delay = 50) {
this.batchFn = batchFn;
this.delay = delay;
this.queue = [];
this.timeoutId = null;
}
request(id) {
return new Promise((resolve, reject) => {
this.queue.push({ id, resolve, reject });
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => this.flush(), this.delay);
});
}
async flush() {
const batch = this.queue.splice(0);
const ids = batch.map(item => item.id);
try {
const results = await this.batchFn(ids);
batch.forEach((item, index) => {
item.resolve(results[index]);
});
} catch (error) {
batch.forEach(item => item.reject(error));
}
}
}
// Usage
const userBatcher = new RequestBatcher(async (ids) => {
const response = await fetch('/api/users/batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids })
});
return await response.json();
});
// These get batched together
const user1 = await userBatcher.request(1);
const user2 = await userBatcher.request(2);
const user3 = await userBatcher.request(3);
response.ok before parsing - fetch doesn't reject on HTTP errorsMaster error management and debugging techniques for robust applications
Handle errors gracefully with try/catch blocks to prevent application crashes.
// Basic try/catch
try {
const result = riskyOperation();
console.log(result);
} catch (error) {
console.error('Error occurred:', error);
}
// Finally block - always executes
try {
connectToDatabase();
performQuery();
} catch (error) {
console.error('Database error:', error);
} finally {
disconnectFromDatabase(); // Always runs
}
// Catch error details
try {
JSON.parse('invalid json');
} catch (error) {
console.log('Name:', error.name); // 'SyntaxError'
console.log('Message:', error.message); // Description
console.log('Stack:', error.stack); // Stack trace
}
// Multiple operations
try {
const data = fetchData();
const parsed = JSON.parse(data);
const validated = validateData(parsed);
processData(validated);
} catch (error) {
// Single catch handles all errors
console.error('Pipeline failed:', error);
}
// Nested try/catch
try {
try {
innerOperation();
} catch (innerError) {
console.log('Inner error:', innerError);
throw innerError; // Re-throw to outer catch
}
} catch (outerError) {
console.log('Outer error:', outerError);
}
// Try/catch with async code (needs await)
try {
const response = await fetch('/api/data');
const data = await response.json();
} catch (error) {
console.error('Fetch failed:', error);
}
// Try/catch doesn't catch async errors without await
try {
fetch('/api/data').then(r => r.json()); // Error not caught!
} catch (error) {
// This won't catch fetch errors
}
// Correct async error handling
fetch('/api/data')
.then(r => r.json())
.catch(error => {
console.error('Fetch failed:', error);
});
Create and throw custom errors for better error handling and debugging.
// Throw built-in Error
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
try {
divide(10, 0);
} catch (error) {
console.error(error.message); // 'Division by zero'
}
// Throw any value (not recommended)
throw 'Simple string error'; // Works but less useful
throw { message: 'Error object' }; // Works
throw 42; // Works but confusing
// Throw with error type
throw new TypeError('Expected a number');
throw new RangeError('Value out of range');
throw new ReferenceError('Variable not defined');
// Custom error class
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
function validateEmail(email) {
if (!email) {
throw new ValidationError('Email is required', 'email');
}
if (!email.includes('@')) {
throw new ValidationError('Invalid email format', 'email');
}
return true;
}
try {
validateEmail('invalid');
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation failed for ${error.field}: ${error.message}`);
} else {
console.error('Unexpected error:', error);
}
}
// Multiple custom error types
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
class AuthenticationError extends Error {
constructor(message) {
super(message);
this.name = 'AuthenticationError';
}
}
async function fetchData(url) {
const response = await fetch(url);
if (response.status === 401) {
throw new AuthenticationError('Not authenticated');
}
if (!response.ok) {
throw new NetworkError(
`HTTP error ${response.status}`,
response.status
);
}
return await response.json();
}
// Handle different error types
try {
await fetchData('/api/data');
} catch (error) {
if (error instanceof AuthenticationError) {
redirectToLogin();
} else if (error instanceof NetworkError) {
showNetworkError(error.statusCode);
} else {
showGenericError();
}
}
JavaScript has several built-in error types for different error conditions.
// Error - Generic error
throw new Error('Something went wrong');
// TypeError - Wrong type
const num = 42;
// num.toUpperCase(); // TypeError: num.toUpperCase is not a function
function expectString(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
}
// RangeError - Number out of range
function setAge(age) {
if (age < 0 || age > 150) {
throw new RangeError('Age must be between 0 and 150');
}
}
// ReferenceError - Variable not found
try {
console.log(undefinedVariable); // ReferenceError
} catch (error) {
console.log(error.name); // 'ReferenceError'
}
// SyntaxError - Invalid syntax
try {
eval('{ invalid syntax }'); // SyntaxError
} catch (error) {
console.log(error.name); // 'SyntaxError'
}
// URIError - Invalid URI encoding
try {
decodeURIComponent('%'); // URIError
} catch (error) {
console.log(error.name); // 'URIError'
}
// EvalError - Error in eval() (rarely used)
// Mostly deprecated in modern JavaScript
// Catching specific error types
try {
riskyOperation();
} catch (error) {
if (error instanceof TypeError) {
console.log('Type error:', error.message);
} else if (error instanceof RangeError) {
console.log('Range error:', error.message);
} else if (error instanceof ReferenceError) {
console.log('Reference error:', error.message);
} else {
console.log('Unknown error:', error);
}
}
// Check error by name
try {
riskyOperation();
} catch (error) {
switch (error.name) {
case 'TypeError':
handleTypeError(error);
break;
case 'RangeError':
handleRangeError(error);
break;
default:
handleGenericError(error);
}
}
Stack traces show the call stack when an error occurred, essential for debugging.
// Generate stack trace
function level3() {
throw new Error('Error at level 3');
}
function level2() {
level3();
}
function level1() {
level2();
}
try {
level1();
} catch (error) {
console.log(error.stack);
/*
Error: Error at level 3
at level3 (script.js:2:9)
at level2 (script.js:6:3)
at level1 (script.js:10:3)
at :1:1
*/
}
// Get stack trace without error
function getStackTrace() {
const stack = new Error().stack;
return stack;
}
console.log(getStackTrace());
// Custom stack trace
class CustomError extends Error {
constructor(message, details) {
super(message);
this.name = 'CustomError';
this.details = details;
// Maintain proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError);
}
}
}
// Parse stack trace
function parseStackTrace(error) {
const lines = error.stack.split('\n');
const frames = [];
for (const line of lines) {
const match = line.match(/at (.+) \((.+):(\d+):(\d+)\)/);
if (match) {
frames.push({
function: match[1],
file: match[2],
line: parseInt(match[3]),
column: parseInt(match[4])
});
}
}
return frames;
}
try {
level1();
} catch (error) {
const frames = parseStackTrace(error);
console.log('Call stack:', frames);
}
Browser console provides powerful debugging methods beyond console.log.
// Basic logging
console.log('Regular message');
console.info('Info message');
console.warn('Warning message');
console.error('Error message');
// Multiple arguments
console.log('User:', user, 'Count:', count);
// String formatting
console.log('Hello %s', 'World');
console.log('Number: %d', 42);
console.log('Object: %o', { name: 'John' });
// CSS styling
console.log('%cStyled text', 'color: blue; font-size: 20px; font-weight: bold');
console.log(
'%cError: %cSomething failed',
'color: red; font-weight: bold',
'color: black'
);
// Group related logs
console.group('User Details');
console.log('Name:', 'John');
console.log('Age:', 30);
console.log('Email:', 'john@example.com');
console.groupEnd();
// Collapsed group
console.groupCollapsed('Advanced Settings');
console.log('Setting 1:', true);
console.log('Setting 2:', false);
console.groupEnd();
// Table display
const users = [
{ name: 'John', age: 30, city: 'NYC' },
{ name: 'Jane', age: 25, city: 'LA' },
{ name: 'Bob', age: 35, city: 'Chicago' }
];
console.table(users);
console.table(users, ['name', 'age']); // Select columns
// Count occurrences
console.count('Button clicked');
console.count('Button clicked');
console.count('Button clicked');
// Button clicked: 1
// Button clicked: 2
// Button clicked: 3
console.countReset('Button clicked');
// Timing
console.time('Operation');
// ... some operation
console.timeLog('Operation', 'Checkpoint');
// ... more work
console.timeEnd('Operation');
// Assertions
console.assert(true, 'This is fine'); // Nothing logged
console.assert(false, 'This will show'); // Logs error
console.assert(1 === 2, 'Math is broken');
// Trace - show call stack
function foo() {
function bar() {
console.trace('Trace from bar');
}
bar();
}
foo();
// Clear console
console.clear();
// Custom logger
class Logger {
constructor(prefix) {
this.prefix = prefix;
}
log(message) {
console.log(`[${this.prefix}]`, message);
}
error(message) {
console.error(`[${this.prefix}]`, message);
}
warn(message) {
console.warn(`[${this.prefix}]`, message);
}
}
const logger = new Logger('MyApp');
logger.log('Application started');
logger.error('Something failed');
The debugger statement pauses execution for debugging in browser DevTools.
// Debugger statement pauses execution
function calculateTotal(items) {
let total = 0;
debugger; // Execution pauses here if DevTools open
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}
// Conditional debugging
function processData(data) {
if (data.length > 1000) {
debugger; // Only pause for large datasets
}
return data.map(item => item * 2);
}
// Debug complex conditions
function findBug(arr) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] < 0) {
debugger; // Pause when negative found
}
}
}
// Production safeguard
const DEBUG = process.env.NODE_ENV === 'development';
function riskyOperation() {
if (DEBUG) {
debugger;
}
// Operation code
}
// Debug wrapper
function debug(fn) {
return function(...args) {
debugger;
return fn.apply(this, args);
};
}
const debuggedFunction = debug(myFunction);
// Breakpoint in catch
try {
riskyOperation();
} catch (error) {
debugger; // Inspect error state
console.error(error);
}
Browser DevTools provide powerful debugging features beyond code-based debugging.
// DevTools Breakpoints:
// 1. Line breakpoints - Click line number
// 2. Conditional breakpoints - Right-click line number
// 3. Logpoints - Log without pausing
// 4. DOM breakpoints - Break on DOM changes
// 5. Event listener breakpoints - Break on events
// 6. XHR breakpoints - Break on network requests
// Conditional breakpoint example
function processItem(item) {
// Set breakpoint with condition: item.id === 123
console.log('Processing:', item.id);
return item.value * 2;
}
// Watch expressions in DevTools
// Add expressions to watch panel to monitor values
// Call stack navigation
function level3() {
// When paused here, see full call stack
return 'result';
}
function level2() {
return level3();
}
function level1() {
return level2();
}
// Scope inspection
function outer() {
const outerVar = 'outer';
function inner() {
const innerVar = 'inner';
debugger; // Inspect both scopes
}
inner();
}
// Step through code
// - Step over (F10): Execute current line
// - Step into (F11): Enter function call
// - Step out (Shift+F11): Exit current function
// - Continue (F8): Resume execution
// Console evaluation during pause
// Type expressions in console to inspect state
// e.g., variables, function calls, etc.
// Source maps
// Enable in DevTools to debug original source
// Even when code is minified/transpiled
// Network debugging
// Monitor fetch requests in Network tab
// Set XHR breakpoints for API calls
// Performance profiling
// Use Performance tab to find bottlenecks
// Record and analyze frame rates
// Memory leaks
// Use Memory tab to detect leaks
// Take heap snapshots and compare
Source maps enable debugging of minified/transpiled code by mapping back to original source.
// Webpack source map configuration
// webpack.config.js
module.exports = {
mode: 'development',
devtool: 'source-map', // Generate source maps
// Other options:
// 'eval' - fastest, lowest quality
// 'inline-source-map' - embedded in bundle
// 'hidden-source-map' - no reference in bundle
// 'nosources-source-map' - no source code
};
// Vite source map
// vite.config.js
export default {
build: {
sourcemap: true
}
};
// Source map comment in generated file
// At end of minified file:
//# sourceMappingURL=app.min.js.map
// Source map file (app.min.js.map)
{
"version": 3,
"sources": ["src/app.js", "src/utils.js"],
"names": ["myFunction", "result"],
"mappings": "AAAA,SAASA,WAAW..."
}
// Production considerations
// Option 1: Don't deploy source maps
// Option 2: Deploy to separate server
// Option 3: Require authentication
// Error reporting with source maps
// Services like Sentry can use source maps
// to show original code in error reports
Effective debugging strategies and patterns for faster problem resolution.
// 1. Reproduce the bug consistently
function reproduceBug() {
// Document exact steps to trigger bug
// Create minimal test case
// Isolate the problem
}
// 2. Use meaningful log messages
// ❌ Bad
console.log(data);
// ✅ Good
console.log('API response received:', data);
console.log('User validation failed:', {
user,
validationErrors
});
// 3. Log at strategic points
function processOrder(order) {
console.log('Processing order:', order.id);
const validated = validateOrder(order);
console.log('Validation result:', validated);
const saved = saveOrder(order);
console.log('Save result:', saved);
return saved;
}
// 4. Use try/catch strategically
async function robustFetch(url) {
try {
const response = await fetch(url);
console.log('Response status:', response.status);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
console.log('Data received:', data);
return data;
} catch (error) {
console.error('Fetch failed:', {
url,
error: error.message,
stack: error.stack
});
throw error;
}
}
// 5. Defensive programming
function safeOperation(value) {
// Validate inputs
if (!value) {
console.warn('Invalid value provided');
return null;
}
// Check preconditions
if (typeof value !== 'number') {
throw new TypeError('Expected number');
}
// Perform operation
return value * 2;
}
// 6. Use assertions for assumptions
function divide(a, b) {
console.assert(typeof a === 'number', 'a must be number');
console.assert(typeof b === 'number', 'b must be number');
console.assert(b !== 0, 'b cannot be zero');
return a / b;
}
// 7. Create helper debugging functions
function debugObject(obj, label = 'Object') {
console.group(label);
console.log('Type:', typeof obj);
console.log('Keys:', Object.keys(obj));
console.log('Values:', Object.values(obj));
console.table(obj);
console.groupEnd();
}
// 8. Time operations
function timeOperation(fn, label) {
console.time(label);
const result = fn();
console.timeEnd(label);
return result;
}
// 9. Binary search debugging
// Comment out half of code to isolate issue
// Repeat until you find the problematic section
Modern JavaScript project structure, modules, and build tools
ES Modules (ESM) provide a standardized module system built into JavaScript for organizing code.
// math.js - Named exports
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export const PI = 3.14159;
// app.js - Import named exports
import { add, subtract, PI } from './math.js';
console.log(add(5, 3)); // 8
// Import everything as namespace
import * as Math from './math.js';
console.log(Math.add(5, 3));
console.log(Math.PI);
// Export list syntax
// utils.js
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
return a / b;
}
export { multiply, divide };
// Import with rename
import { multiply as mult } from './utils.js';
// Re-export from another module
export { add, subtract } from './math.js';
export * from './math.js';
Default exports provide a single main export per module, while named exports allow multiple exports.
// user.js - Default export
export default class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
// Import default (any name works)
import User from './user.js';
import MyUser from './user.js'; // Same thing
const user = new User('John');
// Mixed: default + named exports
// config.js
export default {
apiUrl: 'https://api.example.com',
timeout: 5000
};
export const VERSION = '1.0.0';
export const DEBUG = true;
// Import both
import config, { VERSION, DEBUG } from './config.js';
// Default export patterns
// Function
export default function greet(name) {
return `Hello, ${name}!`;
}
// Object
export default {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
// Class
export default class Calculator {
add(a, b) { return a + b; }
}
// Best practices
// ❌ Don't mix default with many named exports
// ❌ export default { func1, func2, func3, func4, func5 };
// ✅ Use named exports for multiple utilities
export { func1, func2, func3, func4, func5 };
// ✅ Use default for main component/class
export default class MainComponent {}
Rename exports and imports to avoid naming conflicts and improve clarity.
// Export with rename
// validators.js
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function validatePhone(phone) {
return /^\d{10}$/.test(phone);
}
export {
validateEmail as isValidEmail,
validatePhone as isValidPhone
};
// Import with rename
import { isValidEmail as checkEmail } from './validators.js';
console.log(checkEmail('test@example.com'));
// Avoid naming conflicts
// services.js
export function getUser() { /* API call */ }
export function getPost() { /* API call */ }
// helpers.js
export function getUser() { /* Helper */ }
// app.js - Rename to avoid conflict
import { getUser as getUserFromAPI } from './services.js';
import { getUser as getUserHelper } from './helpers.js';
// Namespace pattern
import * as Services from './services.js';
import * as Helpers from './helpers.js';
Services.getUser();
Helpers.getUser();
Load modules dynamically at runtime for code splitting and lazy loading.
// Static import (always loaded)
import { heavyFunction } from './heavy.js';
// Dynamic import (load on demand)
button.addEventListener('click', async () => {
const module = await import('./heavy.js');
module.heavyFunction();
});
// With destructuring
button.addEventListener('click', async () => {
const { heavyFunction } = await import('./heavy.js');
heavyFunction();
});
// Conditional loading
async function loadTheme(isDark) {
if (isDark) {
const { darkTheme } = await import('./themes/dark.js');
applyTheme(darkTheme);
} else {
const { lightTheme } = await import('./themes/light.js');
applyTheme(lightTheme);
}
}
// Error handling
try {
const module = await import('./module.js');
module.init();
} catch (error) {
console.error('Failed to load module:', error);
}
// Lazy load route components
const routes = {
'/home': () => import('./pages/Home.js'),
'/about': () => import('./pages/About.js'),
'/contact': () => import('./pages/Contact.js')
};
async function navigate(path) {
const loadComponent = routes[path];
if (loadComponent) {
const { default: Component } = await loadComponent();
renderComponent(Component);
}
}
// Pre-loading
const preloadModule = import('./module.js');
// Later...
const module = await preloadModule;
Modern bundlers eliminate unused code (dead code elimination) for smaller bundles.
// utils.js - Export individual functions
export function used() {
return 'This is used';
}
export function unused() {
return 'This will be removed';
}
export function alsoUnused() {
return 'This too';
}
// app.js - Only import what you need
import { used } from './utils.js';
console.log(used());
// unused() and alsoUnused() will be removed by bundler
// ❌ Bad for tree shaking - imports everything
import * as Utils from './utils.js';
// ✅ Good for tree shaking - imports only used
import { used } from './utils.js';
// ❌ Default exports harder to tree shake
export default {
used: () => {},
unused: () => {},
alsoUnused: () => {}
};
// ✅ Named exports enable better tree shaking
export function used() {}
export function unused() {}
// Side effects
// If module has side effects, mark it in package.json
// package.json
{
"sideEffects": false // No side effects, safe to tree shake
}
// Or specify files with side effects
{
"sideEffects": ["*.css", "./src/polyfills.js"]
}
npm is the package manager for JavaScript, managing dependencies and scripts.
// Initialize new project
// npm init
// npm init -y (skip questions)
// package.json structure
{
"name": "my-project",
"version": "1.0.0",
"description": "My JavaScript project",
"main": "index.js",
"scripts": {
"start": "node index.js",
"build": "webpack",
"test": "jest",
"dev": "webpack serve --mode development"
},
"dependencies": {
"lodash": "^4.17.21",
"axios": "^1.6.0"
},
"devDependencies": {
"webpack": "^5.89.0",
"jest": "^29.7.0"
}
}
// Install packages
// npm install lodash
// npm install --save-dev webpack (dev dependency)
// npm install -g npm (global)
// Semver versions
// ^1.2.3 - Compatible (1.x.x)
// ~1.2.3 - Approximately (1.2.x)
// 1.2.3 - Exact version
// * or latest - Latest version
// Using installed packages
import _ from 'lodash';
import axios from 'axios';
const nums = [1, 2, 3, 4, 5];
console.log(_.sum(nums));
// Scripts
// npm start
// npm run build
// npm test
// npm run dev
// Useful commands
// npm list - Show installed packages
// npm outdated - Check for updates
// npm update - Update packages
// npm uninstall lodash - Remove package
// npm ci - Clean install (CI/CD)
Define custom scripts in package.json for common development tasks.
// package.json
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src/**/*.js",
"lint:fix": "eslint src/**/*.js --fix",
"format": "prettier --write 'src/**/*.js'",
"clean": "rm -rf dist",
"prebuild": "npm run clean",
"postbuild": "npm run test",
"deploy": "npm run build && npm run upload"
}
}
// Run scripts
// npm start (reserved name, no 'run' needed)
// npm test (reserved name)
// npm run build
// npm run lint:fix
// Pre and post hooks
// prebuild runs before build
// postbuild runs after build
// Pass arguments
// npm run build -- --watch
// package.json: "build": "webpack"
// Executes: webpack --watch
// Environment variables
{
"scripts": {
"start:prod": "NODE_ENV=production node server.js",
"start:dev": "NODE_ENV=development node server.js"
}
}
// Cross-platform with cross-env
{
"scripts": {
"start": "cross-env NODE_ENV=production node server.js"
}
}
Understand the difference between production and development dependencies.
// dependencies - Required in production
{
"dependencies": {
"express": "^4.18.0",
"axios": "^1.6.0",
"lodash": "^4.17.21"
}
}
// Install: npm install express
// devDependencies - Only for development
{
"devDependencies": {
"webpack": "^5.89.0",
"babel-loader": "^9.1.0",
"jest": "^29.7.0",
"eslint": "^8.55.0",
"prettier": "^3.1.0"
}
}
// Install: npm install --save-dev webpack
// peerDependencies - Required by consumers
{
"peerDependencies": {
"react": "^18.0.0"
}
}
// optionalDependencies - Nice to have
{
"optionalDependencies": {
"fsevents": "^2.3.0"
}
}
// When to use what?
/*
dependencies:
- Runtime libraries (lodash, axios)
- Framework code (react, vue)
- Server dependencies (express)
devDependencies:
- Build tools (webpack, vite)
- Testing (jest, mocha)
- Linting (eslint, prettier)
- Dev servers
*/
// Production install (skip devDependencies)
// npm install --production
// npm ci --production
Bundlers combine modules, optimize code, and prepare for production deployment.
// Webpack - Most configurable
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};
// Vite - Fast and modern
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
root: './src',
build: {
outDir: '../dist'
},
server: {
port: 3000
}
});
// Parcel - Zero config
// No config needed! Just:
// parcel index.html
// Comparison
const comparison = {
webpack: {
speed: 'Slower',
config: 'Complex',
features: 'Most comprehensive',
useCase: 'Large, complex projects'
},
vite: {
speed: 'Very fast (ESM + esbuild)',
config: 'Simple',
features: 'Modern, opinionated',
useCase: 'Modern frameworks, fast dev'
},
parcel: {
speed: 'Fast',
config: 'Zero config',
features: 'Automatic',
useCase: 'Quick projects, prototypes'
}
};
// Common features
/*
- Code splitting
- Tree shaking
- Minification
- Source maps
- Hot Module Replacement (HMR)
- Asset optimization
- CSS/Image processing
*/
Use modern class syntax to model data, behavior, and relationships.
ES6 classes are syntax sugar over prototypes. Define constructors, instance methods, getters/setters, statics, and inheritance hierarchies. Use them when they improve clarity, otherwise prefer small composable functions.
Declare classes with constructors and methods. Instances are created via new.
class User {
constructor(name, role = 'viewer') {
this.name = name;
this.role = role;
}
describe() {
return `${this.name} (${this.role})`;
}
}
const u = new User('Rae', 'admin');
console.log(u.describe());
Encapsulate derived values or validation logic using getter and setter syntax.
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
get area() {
return this.width * this.height;
}
set area(value) {
const side = Math.sqrt(value);
this.width = side;
this.height = side;
}
}
const square = new Rectangle(4, 4);
console.log(square.area);
square.area = 81;
console.log(square.width, square.height);
Statics live on the class constructor itself, not instances. Use them for utilities or factories.
class Id {
static prefix = 'usr';
static nextId = 1;
static generate() {
return `${this.prefix}-${this.nextId++}`;
}
}
console.log(Id.generate());
console.log(Id.generate());
Extend classes to reuse behavior. Call super to invoke parent constructors or methods.
class Service {
constructor(name) {
this.name = name;
}
status() {
return `${this.name} ready`;
}
}
class EmailService extends Service {
constructor(name, sender) {
super(name);
this.sender = sender;
}
status() {
return `${super.status()} from ${this.sender}`;
}
}
const mailer = new EmailService('Mailer', 'noreply@example.com');
console.log(mailer.status());
Use # prefixed fields for per-instance privacy.
class Counter {
#value = 0;
increment() {
this.#value++;
return this.#value;
}
get value() {
return this.#value;
}
}
const c = new Counter();
console.log(c.increment());
console.log(c.value);
// c.#value; // SyntaxError
Favor composition to mix capabilities without deep hierarchies.
const canLog = state => ({
log(msg) {
state.logs.push(msg);
return state.logs;
}
});
const canToggle = state => ({
toggle() {
state.enabled = !state.enabled;
return state.enabled;
}
});
const createFeature = name => {
const state = { name, enabled: false, logs: [] };
return { ...state, ...canLog(state), ...canToggle(state) };
};
console.log(createFeature('Search').toggle());
Remember that class methods are not auto-bound; losing this context can break code.
class Button {
constructor(label) {
this.label = label;
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
return `${this.label} clicked`;
}
}
const b = new Button('Save');
const handler = b.handleClick;
console.log(handler());
Public fields and arrow methods are initialized per instance and auto-bind this.
class Toggle {
state = false;
label;
constructor(label) {
this.label = label;
}
flip = () => {
this.state = !this.state;
return `${this.label}: ${this.state}`;
};
}
const t = new Toggle('Feature');
const action = t.flip;
console.log(action()); // still bound
Keep classes small, single-purpose, and favor dependency injection for testability.
class ReportService {
constructor(fetcher) {
this.fetcher = fetcher;
}
async load(id) {
const data = await this.fetcher(`/reports/${id}`);
return data;
}
}
const service = new ReportService((url) => Promise.resolve({ url }));
service.load(7).then(console.log);
super calls; override a method while reusing parent logic.this when passing callbacks to setTimeout.toString() on a class to customize logging.extends and super; composition remains a flexible alternative.this.Continue with Promises and Async/Await to orchestrate asynchronous flows, or revisit Prototypes and Inheritance for the underlying mechanics.
Share behavior across objects with prototype chains and reusable blueprints.
JavaScript uses prototypes instead of classical classes at its core. Every object links to a prototype that supplies shared properties. Understand __proto__, constructor functions, Object.create, and how to attach methods to the prototype to avoid duplication.
Objects delegate property lookups to their prototype via the internal [[Prototype]] link.
const base = { alive: true };
const user = { name: 'Ada', __proto__: base };
console.log(user.name);
console.log(user.alive); // found on base
const proto = Object.getPrototypeOf(user);
console.log(proto === base);
Create objects with a chosen prototype without invoking constructors.
const personProto = {
describe() {
return `${this.name} (${this.role})`;
}
};
function makePerson(name, role) {
const person = Object.create(personProto);
person.name = name;
person.role = role;
return person;
}
const dev = makePerson('Lin', 'Engineer');
console.log(dev.describe());
Before classes, constructor functions paired with new set up instances and prototypes.
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.honk = function() {
return `${this.make} ${this.model} says beep`;
};
const car = new Car('Honda', 'Civic');
console.log(car.honk());
car.wheels = 4;
console.log(car.wheels); // own property
console.log(car.honk === Car.prototype.honk); // shared
Link prototypes to share behavior across hierarchies.
function Vehicle(type) {
this.type = type;
}
Vehicle.prototype.describe = function() {
return `Type: ${this.type}`;
};
function Truck(make, capacity) {
Vehicle.call(this, 'truck');
this.make = make;
this.capacity = capacity;
}
Truck.prototype = Object.create(Vehicle.prototype);
Truck.prototype.constructor = Truck;
Truck.prototype.load = function(amount) {
return `${this.make} loading ${amount} tons`;
};
const t = new Truck('Volvo', 10);
console.log(t.describe());
console.log(t.load(5));
Prefer Object.setPrototypeOf for updates; avoid mutating __proto__ in performance-sensitive code.
const metrics = { track() { return 'tracking'; } };
const feature = { name: 'Search' };
Object.setPrototypeOf(feature, metrics);
console.log(feature.track());
Differentiate own properties from inherited ones when iterating.
console.log('honk' in car); // true (inherited)
console.log(car.hasOwnProperty('honk')); // false
console.log(car.hasOwnProperty('wheels')); // true
Avoid overwriting prototypes after instances exist; attach methods once to save memory.
function Widget(name) {
this.name = name;
// this.render = () => `${this.name}`; // new function per instance
}
Widget.prototype.render = function() {
return `Render ${this.name}`;
};
const w1 = new Widget('Chart');
const w2 = new Widget('Table');
console.log(w1.render === w2.render); // true
ES6 classes wrap prototype mechanics with clearer syntax but retain the same inheritance model.
class Animal {
speak() {
return 'noise';
}
}
class Dog extends Animal {
speak() {
return 'woof';
}
}
const d = new Dog();
console.log(d.speak());
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype);
Derived prototypes override shared methods to specialize behavior while keeping a common interface.
function Notifier() {}
Notifier.prototype.send = function(message) {
return `Sending: ${message}`;
};
function EmailNotifier() {}
EmailNotifier.prototype = Object.create(Notifier.prototype);
EmailNotifier.prototype.constructor = EmailNotifier;
EmailNotifier.prototype.send = function(message) {
return `Email -> ${message}`;
};
function SmsNotifier() {}
SmsNotifier.prototype = Object.create(Notifier.prototype);
SmsNotifier.prototype.constructor = SmsNotifier;
SmsNotifier.prototype.send = function(message) {
return `SMS -> ${message}`;
};
const channels = [new EmailNotifier(), new SmsNotifier()];
channels.forEach(channel => console.log(channel.send('Alert!')));
Never modify Object.prototype in shared code; it can break iteration and third-party libraries.
// Avoid doing this globally
// Object.prototype.log = function() { console.log(this); };
const safeHelpers = {
log(obj) {
console.log(obj);
}
};
safeHelpers.log({ ok: true });
Object.create and add shared methods to its prototype object.Object.create and reset constructor.in and hasOwnProperty.Object.getPrototypeOf to inspect an object's chain and log each step until null.Object.setPrototypeOf and observe new behavior.Object.create builds objects with explicit prototypes; constructor functions plus new set instance state.Object.setPrototypeOf cautiously and prefer setup-time links.Proceed to Classes and OOP to see modern syntax for prototypes, or revisit Scope and Hoisting to understand how prototype methods access variables.
Use string helpers and regex patterns to clean, validate, and transform text safely.
String methods handle many day-to-day tasks without the extra complexity of regex.
const text = ' Learn JavaScript Faster ';
console.log(text.length);
console.log(text.trim());
console.log(text.toLowerCase());
console.log(text.toUpperCase());
console.log(text.includes('JavaScript'));
console.log(text.startsWith(' Learn'));
console.log(text.endsWith('Faster '));
console.log(text.slice(2, 7));
console.log(text.replace('Faster', 'Better'));
console.log(text.split(' '));
Normalize user input before validation so your checks stay predictable.
function normalizeName(value) {
return value
.trim()
.replace(/\s+/g, ' ')
.toLowerCase()
.replace(/\b\w/g, char => char.toUpperCase());
}
console.log(normalizeName(' john doe ')); // John Doe
function cleanSearchTerm(value) {
return value.trim().replace(/[^\w\s-]/g, '');
}
console.log(cleanSearchTerm('learn js!!! now@@')); // learn js now
Learn the symbols that make regex patterns powerful.
console.log(/\d/.test('7')); // true
console.log(/\d+/.test('12345')); // true
console.log(/[a-z]+/i.test('Hello')); // true
console.log(/\s/.test('hello world')); // true
console.log(/^\w+$/.test('user_123')); // true
console.log(/cat|dog/.test('dog')); // true
// Quantifiers
console.log(/\d{4}/.test('2024')); // true
console.log(/\d{2,4}/.test('123')); // true
console.log(/colou?r/.test('color')); // true
console.log(/colou?r/.test('colour')); // true
// Anchors
console.log(/^hello/.test('hello there')); // true
console.log(/world$/.test('hello world')); // true
Flags change how a regex searches text.
const message = 'JavaScript js JS';
console.log(message.match(/js/i)); // first match, case-insensitive
console.log(message.match(/js/gi)); // all matches, case-insensitive
const multiLine = `first line
second line
third line`;
console.log(/^second/m.test(multiLine)); // true
console.log(/line$/m.test(multiLine)); // true
Use regex to validate structured input like emails, usernames, and codes.
const patterns = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
username: /^[a-zA-Z0-9_]{3,16}$/,
pinCode: /^\d{4}$/,
phone: /^\+?1?\d{10,14}$/
};
console.log(patterns.email.test('user@example.com')); // true
console.log(patterns.email.test('wrong@com')); // false
console.log(patterns.username.test('john_doe')); // true
console.log(patterns.username.test('ab')); // false
console.log(patterns.pinCode.test('4821')); // true
console.log(patterns.phone.test('1234567890')); // true
Regex becomes especially useful when transforming text or formatting values.
const sentence = 'I have 2 apples and 5 oranges';
const doubled = sentence.replace(/\d+/g, value => String(Number(value) * 2));
console.log(doubled); // I have 4 apples and 10 oranges
function formatPhone(value) {
const digits = value.replace(/\D/g, '');
return digits.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
console.log(formatPhone('1234567890'));
function titleCase(value) {
return value.replace(/\b\w/g, char => char.toUpperCase());
}
console.log(titleCase('learn javascript today'));
Capture the parts of a string you care about and turn them into structured data.
const content = 'Contact team@example.com and follow #javascript on 2024-01-15';
const emails = content.match(/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/gi) || [];
const hashtags = content.match(/#\w+/g) || [];
const dateParts = '2024-01-15'.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(emails); // ['team@example.com']
console.log(hashtags); // ['#javascript']
console.log(dateParts[1]); // 2024
console.log(dateParts[2]); // 01
console.log(dateParts[3]); // 15
Each regex method is useful in a slightly different situation.
const source = 'Order ids: 101, 205, 309';
const idPattern = /\d+/g;
console.log(idPattern.test(source)); // true
idPattern.lastIndex = 0;
console.log(idPattern.exec(source)); // first detailed match
idPattern.lastIndex = 0;
console.log(source.match(idPattern)); // all values
console.log('one,two;three'.split(/[,;]/)); // ['one', 'two', 'three']
Lookaheads let you validate multiple rules in one compact expression.
const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
console.log(passwordPattern.test('Passw0rd')); // true
console.log(passwordPattern.test('password')); // false
function validatePassword(password) {
return {
length: /.{8,}/.test(password),
lowercase: /[a-z]/.test(password),
uppercase: /[A-Z]/.test(password),
digit: /\d/.test(password)
};
}
console.log(validatePassword('Passw0rd'));
Master date manipulation, formatting, and timezone handling
Create Date objects using various methods for different use cases.
// Current date and time
const now = new Date();
console.log(now); // Current date/time
// From timestamp (milliseconds since Jan 1, 1970 UTC)
const fromTimestamp = new Date(1705312800000);
console.log(fromTimestamp);
// From date string
const fromString = new Date('2024-01-15');
const fromISO = new Date('2024-01-15T10:30:00Z');
console.log(fromString);
// From date components (month is 0-indexed!)
const fromComponents = new Date(2024, 0, 15); // Jan 15, 2024
const withTime = new Date(2024, 0, 15, 10, 30, 0, 0); // 10:30:00.000
// Month is 0-indexed: 0=Jan, 11=Dec
console.log(new Date(2024, 0, 1)); // January 1
console.log(new Date(2024, 11, 31)); // December 31
// Invalid dates
const invalid = new Date('invalid');
console.log(invalid); // Invalid Date
console.log(isNaN(invalid)); // true
// Check if date is valid
function isValidDate(date) {
return date instanceof Date && !isNaN(date);
}
console.log(isValidDate(new Date())); // true
console.log(isValidDate(new Date('invalid'))); // false
// Copy a date
const original = new Date();
const copy = new Date(original);
console.log(copy.getTime() === original.getTime()); // true
// UTC dates
const utcDate = new Date(Date.UTC(2024, 0, 15, 10, 30));
console.log(utcDate.toISOString());
Work with timestamps for efficient date comparisons and storage.
// Get current timestamp (ms since Jan 1, 1970)
const timestamp1 = Date.now(); // Fastest
const timestamp2 = new Date().getTime();
const timestamp3 = +new Date(); // Unary plus operator
console.log(timestamp1); // e.g., 1705312800000
// Convert date to timestamp
const date = new Date('2024-01-15');
const ms = date.getTime();
console.log(ms);
// Convert timestamp to date
const dateFromMs = new Date(1705312800000);
console.log(dateFromMs);
// Unix timestamp (seconds since epoch)
const unixTimestamp = Math.floor(Date.now() / 1000);
console.log(unixTimestamp); // e.g., 1705312800
// Compare dates using timestamps
const date1 = new Date('2024-01-15');
const date2 = new Date('2024-01-20');
if (date1.getTime() < date2.getTime()) {
console.log('date1 is before date2');
}
// Calculate difference in milliseconds
const diff = date2 - date1; // 432000000 ms
console.log(diff);
// Convert to days
const days = diff / (1000 * 60 * 60 * 24);
console.log(`${days} days`); // 5 days
// Measure execution time
const startTime = Date.now();
// Some operation
for (let i = 0; i < 1000000; i++) {}
const endTime = Date.now();
console.log(`Execution took ${endTime - startTime}ms`);
// Performance.now() for high-precision timing
const start = performance.now();
// Operation
const end = performance.now();
console.log(`Precise timing: ${end - start}ms`);
Extract and modify individual date components with getter and setter methods.
const date = new Date('2024-01-15T10:30:45.123');
// Getters (local time)
console.log(date.getFullYear()); // 2024
console.log(date.getMonth()); // 0 (January, 0-indexed!)
console.log(date.getDate()); // 15 (day of month)
console.log(date.getDay()); // 1 (day of week: 0=Sun, 6=Sat)
console.log(date.getHours()); // 10
console.log(date.getMinutes()); // 30
console.log(date.getSeconds()); // 45
console.log(date.getMilliseconds()); // 123
// UTC getters
console.log(date.getUTCFullYear()); // 2024
console.log(date.getUTCMonth()); // 0
console.log(date.getUTCDate()); // 15
console.log(date.getUTCHours()); // May differ from local
// Setters (local time)
const modifiable = new Date('2024-01-15');
modifiable.setFullYear(2025);
modifiable.setMonth(11); // December
modifiable.setDate(25);
modifiable.setHours(14, 30, 0, 0); // 2:30:00 PM
console.log(modifiable); // 2025-12-25 14:30:00
// UTC setters
modifiable.setUTCHours(10);
modifiable.setUTCMinutes(0);
// Chaining setters
const newDate = new Date()
.setFullYear(2024);
// Add/subtract days
function addDays(date, days) {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
console.log(addDays(new Date(), 7)); // 7 days from now
// Add/subtract months
function addMonths(date, months) {
const result = new Date(date);
result.setMonth(result.getMonth() + months);
return result;
}
console.log(addMonths(new Date(), 3)); // 3 months from now
// Start of day
function startOfDay(date) {
const result = new Date(date);
result.setHours(0, 0, 0, 0);
return result;
}
// End of day
function endOfDay(date) {
const result = new Date(date);
result.setHours(23, 59, 59, 999);
return result;
}
// Start of month
function startOfMonth(date) {
return new Date(date.getFullYear(), date.getMonth(), 1);
}
// End of month
function endOfMonth(date) {
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
}
Convert dates to human-readable strings in various formats.
const date = new Date('2024-01-15T10:30:00');
// ISO 8601 format (standard)
console.log(date.toISOString()); // '2024-01-15T10:30:00.000Z'
// Locale-specific formats
console.log(date.toLocaleString()); // '1/15/2024, 10:30:00 AM' (US)
console.log(date.toLocaleDateString()); // '1/15/2024'
console.log(date.toLocaleTimeString()); // '10:30:00 AM'
// Custom locale
console.log(date.toLocaleDateString('en-GB')); // '15/01/2024'
console.log(date.toLocaleDateString('de-DE')); // '15.1.2024'
console.log(date.toLocaleDateString('ja-JP')); // '2024/1/15'
// Format options
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
console.log(date.toLocaleDateString('en-US', options));
// 'Monday, January 15, 2024'
// Time format options
const timeOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
console.log(date.toLocaleTimeString('en-US', timeOptions));
// '10:30:00'
// Combined date and time
const fullOptions = {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
console.log(date.toLocaleString('en-US', fullOptions));
// 'Jan 15, 2024, 10:30 AM'
// Legacy methods (avoid)
console.log(date.toString()); // Implementation-dependent
console.log(date.toDateString()); // 'Mon Jan 15 2024'
console.log(date.toTimeString()); // '10:30:00 GMT+0000'
console.log(date.toUTCString()); // 'Mon, 15 Jan 2024 10:30:00 GMT'
// Custom formatting function
function formatDate(date, format) {
const pad = (n) => String(n).padStart(2, '0');
const replacements = {
'YYYY': date.getFullYear(),
'MM': pad(date.getMonth() + 1),
'DD': pad(date.getDate()),
'HH': pad(date.getHours()),
'mm': pad(date.getMinutes()),
'ss': pad(date.getSeconds())
};
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => replacements[match]);
}
console.log(formatDate(date, 'YYYY-MM-DD HH:mm:ss'));
// '2024-01-15 10:30:00'
console.log(formatDate(date, 'DD/MM/YYYY'));
// '15/01/2024'
Parse date strings into Date objects, handling various formats.
// ISO 8601 (recommended - unambiguous)
const iso = new Date('2024-01-15T10:30:00Z');
console.log(iso);
// Date.parse() returns timestamp
const timestamp = Date.parse('2024-01-15');
const date = new Date(timestamp);
// Common formats (implementation-dependent!)
const formats = [
'2024-01-15',
'01/15/2024',
'January 15, 2024',
'15 Jan 2024',
'2024-01-15T10:30:00'
];
formats.forEach(format => {
const parsed = new Date(format);
console.log(`${format} => ${parsed}`);
});
// Safe parsing with validation
function parseDate(str) {
const date = new Date(str);
return isNaN(date) ? null : date;
}
const valid = parseDate('2024-01-15');
const invalid = parseDate('invalid');
console.log(valid); // Date object
console.log(invalid); // null
// Parse custom format
function parseCustomDate(str, format = 'YYYY-MM-DD') {
const parts = str.split(/[-/]/);
if (format === 'YYYY-MM-DD') {
return new Date(parts[0], parts[1] - 1, parts[2]);
} else if (format === 'DD/MM/YYYY') {
return new Date(parts[2], parts[1] - 1, parts[0]);
}
return null;
}
console.log(parseCustomDate('15/01/2024', 'DD/MM/YYYY'));
// Parse relative dates
function parseRelativeDate(str) {
const now = new Date();
const matches = str.match(/(\d+)\s+(day|week|month|year)s?\s+ago/);
if (!matches) return null;
const [, amount, unit] = matches;
const num = parseInt(amount);
const result = new Date(now);
switch (unit) {
case 'day':
result.setDate(result.getDate() - num);
break;
case 'week':
result.setDate(result.getDate() - num * 7);
break;
case 'month':
result.setMonth(result.getMonth() - num);
break;
case 'year':
result.setFullYear(result.getFullYear() - num);
break;
}
return result;
}
console.log(parseRelativeDate('3 days ago'));
console.log(parseRelativeDate('2 weeks ago'));
console.log(parseRelativeDate('1 month ago'));
Compare dates for sorting, validation, and time-based logic.
const date1 = new Date('2024-01-15');
const date2 = new Date('2024-01-20');
const date3 = new Date('2024-01-15');
// Direct comparison (compares timestamps)
console.log(date1 < date2); // true
console.log(date1 > date2); // false
console.log(date1 <= date3); // true
// Equality requires timestamp comparison
console.log(date1 == date3); // false (different objects)
console.log(date1.getTime() === date3.getTime()); // true
// Helper functions
function isSameDay(date1, date2) {
return date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
}
function isBefore(date1, date2) {
return date1.getTime() < date2.getTime();
}
function isAfter(date1, date2) {
return date1.getTime() > date2.getTime();
}
function isBetween(date, start, end) {
return date >= start && date <= end;
}
// Check if date is today
function isToday(date) {
return isSameDay(date, new Date());
}
// Check if date is in the past
function isPast(date) {
return date < new Date();
}
// Check if date is in the future
function isFuture(date) {
return date > new Date();
}
// Sort dates
const dates = [
new Date('2024-03-15'),
new Date('2024-01-15'),
new Date('2024-02-15')
];
dates.sort((a, b) => a - b); // Ascending
console.log(dates);
dates.sort((a, b) => b - a); // Descending
console.log(dates);
// Find min/max date
const minDate = new Date(Math.min(...dates));
const maxDate = new Date(Math.max(...dates));
console.log('Earliest:', minDate);
console.log('Latest:', maxDate);
// Age calculation
function calculateAge(birthDate) {
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
const birthDate = new Date('1990-05-15');
console.log(`Age: ${calculateAge(birthDate)}`);
Handle timezones and internationalization with the Intl API.
// Get timezone offset (minutes from UTC)
const date = new Date();
const offsetMinutes = date.getTimezoneOffset();
const offsetHours = -offsetMinutes / 60;
console.log(`Timezone offset: UTC${offsetHours >= 0 ? '+' : ''}${offsetHours}`);
// Intl.DateTimeFormat for timezone-aware formatting
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'America/New_York',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZoneName: 'short'
});
console.log(formatter.format(date));
// 'January 15, 2024 at 10:30 AM EST'
// Multiple timezones
const timezones = ['America/New_York', 'Europe/London', 'Asia/Tokyo'];
timezones.forEach(tz => {
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: tz,
hour: '2-digit',
minute: '2-digit',
timeZoneName: 'short'
});
console.log(`${tz}: ${formatter.format(date)}`);
});
// Convert between timezones
function convertTimezone(date, fromTz, toTz) {
const dateStr = date.toLocaleString('en-US', { timeZone: fromTz });
return new Date(dateStr);
}
// Relative time formatting
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
console.log(rtf.format(-1, 'day')); // 'yesterday'
console.log(rtf.format(1, 'day')); // 'tomorrow'
console.log(rtf.format(-7, 'day')); // '7 days ago'
console.log(rtf.format(2, 'week')); // 'in 2 weeks'
// Time ago helper
function timeAgo(date) {
const seconds = Math.floor((new Date() - date) / 1000);
const intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
second: 1
};
for (const [unit, secondsInUnit] of Object.entries(intervals)) {
const interval = Math.floor(seconds / secondsInUnit);
if (interval >= 1) {
return rtf.format(-interval, unit);
}
}
return 'just now';
}
console.log(timeAgo(new Date(Date.now() - 3600000))); // '1 hour ago'
// ISO 8601 with timezone
function toISOStringWithTimezone(date) {
const offset = -date.getTimezoneOffset();
const sign = offset >= 0 ? '+' : '-';
const hours = Math.floor(Math.abs(offset) / 60).toString().padStart(2, '0');
const minutes = (Math.abs(offset) % 60).toString().padStart(2, '0');
return date.toISOString().slice(0, -1) + sign + hours + ':' + minutes;
}
console.log(toISOStringWithTimezone(new Date()));
Modern libraries provide cleaner APIs and better timezone support than native Date.
// date-fns example (import required)
/*
import {
format,
addDays,
subMonths,
differenceInDays,
isAfter,
parseISO
} from 'date-fns';
const now = new Date();
// Formatting
console.log(format(now, 'yyyy-MM-dd')); // '2024-01-15'
console.log(format(now, 'MMMM do, yyyy')); // 'January 15th, 2024'
// Manipulation
const tomorrow = addDays(now, 1);
const lastMonth = subMonths(now, 1);
// Comparison
const daysDiff = differenceInDays(tomorrow, now); // 1
console.log(isAfter(tomorrow, now)); // true
// Parsing
const date = parseISO('2024-01-15');
*/
// Day.js example (import required)
/*
import dayjs from 'dayjs';
const now = dayjs();
// Formatting
console.log(now.format('YYYY-MM-DD')); // '2024-01-15'
console.log(now.format('MMMM D, YYYY')); // 'January 15, 2024'
// Manipulation
const tomorrow = now.add(1, 'day');
const lastMonth = now.subtract(1, 'month');
// Comparison
console.log(tomorrow.isAfter(now)); // true
console.log(now.isBefore(tomorrow)); // true
// Relative time
console.log(now.from(tomorrow)); // 'in a day'
console.log(tomorrow.fromNow()); // 'in a day'
// Start/end of periods
console.log(now.startOf('month'));
console.log(now.endOf('year'));
*/
// Why use libraries?
const reasons = [
'Immutable operations (native Date is mutable)',
'Better parsing and formatting',
'Timezone support',
'More intuitive API',
'Locale support',
'Relative time formatting',
'Duration calculations',
'Smaller bundle size (especially Day.js: 2KB)'
];
// Native Date limitations
const native = new Date('2024-01-15');
native.setDate(native.getDate() + 1); // Mutates original
console.log(native); // Modified
// Libraries are immutable
// const dayjs1 = dayjs('2024-01-15');
// const dayjs2 = dayjs1.add(1, 'day');
// console.log(dayjs1); // Original unchanged
// console.log(dayjs2); // New instance
Master data serialization and client-side storage solutions
Convert between JavaScript objects and JSON strings for data serialization and transmission.
// Basic JSON.stringify
const user = {
id: 123,
name: 'John Doe',
email: 'john@example.com',
active: true
};
const jsonString = JSON.stringify(user);
console.log(jsonString);
// {"id":123,"name":"John Doe","email":"john@example.com","active":true}
// Pretty print with indentation
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
// {
// "id": 123,
// "name": "John Doe",
// ...
// }
// Basic JSON.parse
const parsedUser = JSON.parse(jsonString);
console.log(parsedUser.name); // "John Doe"
// Parse with error handling
function safeJSONParse(str, fallback = null) {
try {
return JSON.parse(str);
} catch (error) {
console.error('JSON parse error:', error);
return fallback;
}
}
const data = safeJSONParse('invalid json', { default: true });
// Stringify with replacer function
const obj = {
name: 'John',
password: 'secret123',
age: 30
};
const filtered = JSON.stringify(obj, (key, value) => {
if (key === 'password') return undefined; // Exclude password
return value;
});
console.log(filtered); // {"name":"John","age":30}
// Stringify with property whitelist
const whitelisted = JSON.stringify(obj, ['name', 'age']);
console.log(whitelisted); // {"name":"John","age":30}
// Parse with reviver function
const jsonWithDate = '{"name":"John","created":"2024-01-15T10:30:00.000Z"}';
const parsed = JSON.parse(jsonWithDate, (key, value) => {
if (key === 'created') {
return new Date(value); // Convert to Date object
}
return value;
});
console.log(parsed.created instanceof Date); // true
// Handle special values
const special = {
undef: undefined, // Omitted in JSON
nul: null, // Preserved
nan: NaN, // Becomes null
inf: Infinity, // Becomes null
func: () => {}, // Omitted
symbol: Symbol('id'), // Omitted
date: new Date() // Becomes string
};
console.log(JSON.stringify(special));
// {"nul":null,"nan":null,"inf":null,"date":"2024-01-15T10:30:00.000Z"}
Robust JSON parsing requires proper error handling and validation to prevent application crashes.
// Comprehensive error handling
function parseJSON(str, options = {}) {
const {
fallback = null,
schema = null,
onError = console.error
} = options;
try {
const parsed = JSON.parse(str);
// Optional schema validation
if (schema && !validateSchema(parsed, schema)) {
throw new Error('Schema validation failed');
}
return parsed;
} catch (error) {
onError('JSON parse error:', error);
return fallback;
}
}
// Simple schema validator
function validateSchema(data, schema) {
return Object.keys(schema).every(key => {
const expectedType = schema[key];
const actualType = typeof data[key];
return actualType === expectedType;
});
}
// Usage
const userSchema = {
id: 'number',
name: 'string',
email: 'string'
};
const userData = parseJSON('{"id":123,"name":"John","email":"john@example.com"}', {
schema: userSchema,
fallback: { id: 0, name: 'Guest', email: '' }
});
// Safe stringify with circular reference handling
function safeStringify(obj, space = 0) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
}
return value;
}, space);
}
// Test circular reference
const circular = { name: 'Test' };
circular.self = circular;
console.log(safeStringify(circular, 2));
// {
// "name": "Test",
// "self": "[Circular]"
// }
// Validate JSON string before parsing
function isValidJSON(str) {
try {
JSON.parse(str);
return true;
} catch {
return false;
}
}
if (isValidJSON(userInput)) {
const data = JSON.parse(userInput);
processData(data);
}
// Deep clone with JSON (simple objects only)
function deepClone(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (error) {
console.error('Clone failed:', error);
return null;
}
}
const original = { a: 1, b: { c: 2 } };
const clone = deepClone(original);
clone.b.c = 3;
console.log(original.b.c); // Still 2
// Custom toJSON method
class User {
constructor(name, password) {
this.name = name;
this._password = password;
}
toJSON() {
return {
name: this.name,
// Exclude password from serialization
};
}
}
const user = new User('John', 'secret');
console.log(JSON.stringify(user)); // {"name":"John"}
localStorage persists data across browser sessions with no expiration - perfect for user preferences and app state.
// Basic operations
localStorage.setItem('username', 'John Doe');
const username = localStorage.getItem('username');
localStorage.removeItem('username');
localStorage.clear(); // Remove all items
// Store objects (must stringify)
const settings = {
theme: 'dark',
language: 'en',
notifications: true
};
localStorage.setItem('settings', JSON.stringify(settings));
// Retrieve objects (must parse)
const savedSettings = JSON.parse(
localStorage.getItem('settings') || '{}'
);
// Safe storage wrapper
const storage = {
set(key, value) {
try {
const serialized = JSON.stringify(value);
localStorage.setItem(key, serialized);
return true;
} catch (error) {
console.error('Storage error:', error);
return false;
}
},
get(key, fallback = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : fallback;
} catch (error) {
console.error('Retrieval error:', error);
return fallback;
}
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
},
has(key) {
return localStorage.getItem(key) !== null;
}
};
// Usage
storage.set('user', { id: 123, name: 'John' });
const user = storage.get('user', { id: 0, name: 'Guest' });
// Get all keys
function getAllKeys() {
return Object.keys(localStorage);
}
// Get all items
function getAllItems() {
const items = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
items[key] = storage.get(key);
}
return items;
}
// Namespace pattern to avoid conflicts
class NamespacedStorage {
constructor(namespace) {
this.namespace = namespace;
}
_key(key) {
return `${this.namespace}:${key}`;
}
set(key, value) {
storage.set(this._key(key), value);
}
get(key, fallback) {
return storage.get(this._key(key), fallback);
}
remove(key) {
storage.remove(this._key(key));
}
clear() {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith(`${this.namespace}:`)) {
localStorage.removeItem(key);
}
});
}
}
// Usage
const appStorage = new NamespacedStorage('myapp');
appStorage.set('user', { name: 'John' });
appStorage.set('settings', { theme: 'dark' });
// Expiring storage
class ExpiringStorage {
set(key, value, ttl) {
const item = {
value,
expiry: Date.now() + ttl
};
storage.set(key, item);
}
get(key, fallback = null) {
const item = storage.get(key);
if (!item) return fallback;
if (Date.now() > item.expiry) {
storage.remove(key);
return fallback;
}
return item.value;
}
}
const expiring = new ExpiringStorage();
expiring.set('temp', 'data', 60000); // Expires in 1 minute
sessionStorage persists data only for the browser session - perfect for temporary state and form data.
// Same API as localStorage but session-scoped
sessionStorage.setItem('tempData', 'value');
const tempData = sessionStorage.getItem('tempData');
sessionStorage.removeItem('tempData');
sessionStorage.clear();
// Use cases for sessionStorage
// 1. Form data preservation during navigation
function saveFormData(formId) {
const form = document.querySelector(`#${formId}`);
const formData = new FormData(form);
const data = Object.fromEntries(formData);
sessionStorage.setItem(`form_${formId}`, JSON.stringify(data));
}
function restoreFormData(formId) {
const saved = sessionStorage.getItem(`form_${formId}`);
if (!saved) return;
const data = JSON.parse(saved);
const form = document.querySelector(`#${formId}`);
Object.keys(data).forEach(key => {
const input = form.elements[key];
if (input) input.value = data[key];
});
}
// Auto-save form on input
document.querySelector('#myForm')?.addEventListener('input', () => {
saveFormData('myForm');
});
// Restore on page load
window.addEventListener('DOMContentLoaded', () => {
restoreFormData('myForm');
});
// 2. Multi-step form state
class MultiStepForm {
constructor(formId) {
this.formId = formId;
this.storageKey = `multistep_${formId}`;
}
saveStep(step, data) {
const formState = this.getState();
formState[step] = data;
sessionStorage.setItem(this.storageKey, JSON.stringify(formState));
}
getStep(step) {
const formState = this.getState();
return formState[step] || {};
}
getState() {
const saved = sessionStorage.getItem(this.storageKey);
return saved ? JSON.parse(saved) : {};
}
clear() {
sessionStorage.removeItem(this.storageKey);
}
}
// Usage
const form = new MultiStepForm('registration');
form.saveStep('personal', { name: 'John', age: 30 });
form.saveStep('contact', { email: 'john@example.com' });
// 3. Tab-specific state
sessionStorage.setItem('currentTab', 'dashboard');
// localStorage vs sessionStorage comparison
const comparison = {
localStorage: {
scope: 'All tabs/windows',
persistence: 'Permanent',
useCase: 'Settings, preferences, cache'
},
sessionStorage: {
scope: 'Single tab',
persistence: 'Until tab closes',
useCase: 'Form data, temporary state'
}
};
Understand storage capacity limits and respond to storage changes across tabs.
// Check available space (approximate)
function getStorageSize() {
let total = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
total += localStorage[key].length + key.length;
}
}
return (total / 1024).toFixed(2) + ' KB';
}
console.log('Storage used:', getStorageSize());
// Test storage limit
function testStorageLimit() {
const testKey = 'sizeTest';
let size = 0;
try {
// Typical limit: 5-10MB
for (let i = 0; i < 1024; i++) {
localStorage.setItem(testKey, 'x'.repeat(1024 * i));
size = i;
}
} catch (e) {
console.log('Storage limit reached at:', size, 'KB');
localStorage.removeItem(testKey);
}
}
// Handle quota exceeded error
function safeSetItem(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.warn('Storage quota exceeded');
// Strategy: Clear old data
clearOldData();
// Try again
try {
localStorage.setItem(key, value);
return true;
} catch {
return false;
}
}
return false;
}
}
function clearOldData() {
// Remove items with oldest timestamp
const items = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
try {
const item = JSON.parse(localStorage.getItem(key));
if (item.timestamp) {
items.push({ key, timestamp: item.timestamp });
}
} catch {}
}
items.sort((a, b) => a.timestamp - b.timestamp);
// Remove oldest 20%
const toRemove = Math.ceil(items.length * 0.2);
items.slice(0, toRemove).forEach(item => {
localStorage.removeItem(item.key);
});
}
// Storage event - sync across tabs
window.addEventListener('storage', (e) => {
console.log('Storage changed:');
console.log('Key:', e.key);
console.log('Old value:', e.oldValue);
console.log('New value:', e.newValue);
console.log('URL:', e.url);
// React to changes
if (e.key === 'theme') {
applyTheme(e.newValue);
}
if (e.key === 'logout') {
window.location.href = '/login';
}
});
// Broadcast changes across tabs
function broadcastLogout() {
localStorage.setItem('logout', Date.now().toString());
localStorage.removeItem('logout'); // Triggers storage event
}
// Sync state across tabs
class CrossTabSync {
constructor(key) {
this.key = key;
this.listeners = [];
window.addEventListener('storage', (e) => {
if (e.key === this.key && e.newValue) {
const data = JSON.parse(e.newValue);
this.listeners.forEach(fn => fn(data));
}
});
}
set(data) {
localStorage.setItem(this.key, JSON.stringify(data));
}
get() {
const item = localStorage.getItem(this.key);
return item ? JSON.parse(item) : null;
}
onChange(callback) {
this.listeners.push(callback);
}
}
// Usage
const userSync = new CrossTabSync('currentUser');
userSync.onChange((user) => {
console.log('User updated in another tab:', user);
updateUI(user);
});
Cookies are small text files sent with HTTP requests - useful for authentication and tracking.
// Set cookie
document.cookie = 'username=John; max-age=3600; path=/';
// Cookie with expiration date
const expires = new Date();
expires.setDate(expires.getDate() + 7); // 7 days
document.cookie = `token=abc123; expires=${expires.toUTCString()}; path=/`;
// Secure and HttpOnly cookies (requires server)
document.cookie = 'session=xyz; secure; samesite=strict';
// Get cookie value
function getCookie(name) {
const matches = document.cookie.match(
new RegExp('(?:^|; )' + name + '=([^;]*)')
);
return matches ? decodeURIComponent(matches[1]) : null;
}
const username = getCookie('username');
// Get all cookies as object
function getAllCookies() {
return document.cookie.split('; ').reduce((acc, cookie) => {
const [key, value] = cookie.split('=');
acc[key] = decodeURIComponent(value);
return acc;
}, {});
}
// Delete cookie
function deleteCookie(name) {
document.cookie = `${name}=; max-age=0; path=/`;
}
// Cookie helper class
class CookieManager {
static set(name, value, days = 7, options = {}) {
const { path = '/', secure = false, sameSite = 'lax' } = options;
const expires = new Date();
expires.setDate(expires.getDate() + days);
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
cookie += `; expires=${expires.toUTCString()}`;
cookie += `; path=${path}`;
if (secure) cookie += '; secure';
cookie += `; samesite=${sameSite}`;
document.cookie = cookie;
}
static get(name) {
return getCookie(name);
}
static delete(name) {
deleteCookie(name);
}
static has(name) {
return getCookie(name) !== null;
}
}
// Usage
CookieManager.set('preferences', JSON.stringify({ theme: 'dark' }), 30);
const prefs = JSON.parse(CookieManager.get('preferences') || '{}');
// Cookies vs Storage
const storageComparison = {
cookies: {
size: '4KB',
sentToServer: true,
expiration: 'Configurable',
useCase: 'Authentication, tracking'
},
localStorage: {
size: '5-10MB',
sentToServer: false,
expiration: 'Never',
useCase: 'App state, cache'
},
sessionStorage: {
size: '5-10MB',
sentToServer: false,
expiration: 'Session',
useCase: 'Temporary state'
}
};
IndexedDB is a low-level API for storing large amounts of structured data in the browser.
// Open database
function openDB(name, version = 1) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (e) => {
const db = e.target.result;
// Create object store (table)
if (!db.objectStoreNames.contains('users')) {
const store = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
store.createIndex('email', 'email', { unique: true });
store.createIndex('name', 'name', { unique: false });
}
};
});
}
// Add data
async function addUser(user) {
const db = await openDB('myapp');
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
return new Promise((resolve, reject) => {
const request = store.add(user);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// Get data
async function getUser(id) {
const db = await openDB('myapp');
const transaction = db.transaction(['users'], 'readonly');
const store = transaction.objectStore('users');
return new Promise((resolve, reject) => {
const request = store.get(id);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// Get all data
async function getAllUsers() {
const db = await openDB('myapp');
const transaction = db.transaction(['users'], 'readonly');
const store = transaction.objectStore('users');
return new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// Usage
await addUser({ name: 'John', email: 'john@example.com' });
const user = await getUser(1);
const allUsers = await getAllUsers();
// When to use IndexedDB
const indexedDBUseCases = [
'Offline-first applications',
'Large datasets (> 10MB)',
'Complex queries with indexes',
'Binary data (files, images)',
'Progressive Web Apps (PWAs)'
];
Validate data before storing and after retrieving to ensure data integrity.
// Schema validator
class StorageValidator {
constructor(schema) {
this.schema = schema;
}
validate(data) {
const errors = [];
Object.keys(this.schema).forEach(key => {
const rules = this.schema[key];
const value = data[key];
if (rules.required && value === undefined) {
errors.push(`${key} is required`);
}
if (value !== undefined && rules.type && typeof value !== rules.type) {
errors.push(`${key} must be ${rules.type}`);
}
if (rules.min !== undefined && value < rules.min) {
errors.push(`${key} must be >= ${rules.min}`);
}
if (rules.max !== undefined && value > rules.max) {
errors.push(`${key} must be <= ${rules.max}`);
}
if (rules.pattern && !rules.pattern.test(value)) {
errors.push(`${key} format is invalid`);
}
});
return {
valid: errors.length === 0,
errors
};
}
}
// Usage
const userSchema = new StorageValidator({
id: { required: true, type: 'number', min: 1 },
name: { required: true, type: 'string' },
email: {
required: true,
type: 'string',
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
age: { type: 'number', min: 0, max: 150 }
});
function saveUser(user) {
const validation = userSchema.validate(user);
if (!validation.valid) {
console.error('Validation errors:', validation.errors);
return false;
}
storage.set('user', user);
return true;
}
// Versioned storage with migration
class VersionedStorage {
constructor(key, version, migrations) {
this.key = key;
this.version = version;
this.migrations = migrations;
}
get() {
const stored = storage.get(this.key);
if (!stored) return null;
let data = stored.data;
let version = stored.version || 1;
// Run migrations
while (version < this.version) {
const migration = this.migrations[version];
if (migration) {
data = migration(data);
}
version++;
}
return data;
}
set(data) {
storage.set(this.key, {
version: this.version,
data
});
}
}
// Usage with migrations
const userStorage = new VersionedStorage('user', 3, {
2: (data) => ({ ...data, createdAt: Date.now() }),
3: (data) => ({ ...data, settings: { theme: 'light' } })
});
userStorage.set({ name: 'John', email: 'john@example.com' });
const user = userStorage.get(); // Has version 3 structure
Automate quality with Jest or Vitest for unit tests, and ESLint + Prettier for consistent style.
// sum.test.js
import { sum } from './sum';
test('adds numbers', () => {
expect(sum(2, 3)).toBe(5);
});
Last updated: March 2026