Master JavaScript in 10 Days
From first variable to async/await, closures to design patterns — a complete, structured path to professional-grade JavaScript.
Foundations of JavaScript
Understand what JavaScript is, how it runs in the browser and Node.js, and master the absolute basics: variables, data types, and operators.
JavaScript is a high-level, interpreted, dynamically typed programming language. It is the only language that runs natively in web browsers, making it the backbone of interactive web experiences. Today, it also powers servers (Node.js), mobile apps (React Native), and desktop apps (Electron).
Code is executed line-by-line by the JavaScript engine (V8, SpiderMonkey) — no separate compile step needed.
Variables can hold any type and change type at runtime. Types are resolved at execution, not at declaration.
JS runs on one thread but uses an event loop to handle async tasks without blocking.
Supports procedural, object-oriented, and functional programming styles.
Variables are named containers for storing data. JavaScript has three keywords for declaring them, each with different scoping and mutability rules.
| Keyword | Scope | Reassignable | Hoisted |
|---|---|---|---|
| var | Function | Yes | Yes (undefined) |
| let | Block | Yes | No (TDZ) |
| const | Block | No | No (TDZ) |
// var — function-scoped, avoid in modern JS var name = "Alice"; // let — block-scoped, can be reassigned let age = 25; age = 26; // ✓ valid // const — block-scoped, cannot be reassigned const PI = 3.14159; // PI = 3; → TypeError: Assignment to constant variable // const with objects — the binding is const, not the content const user = { name: "Bob" }; user.name = "Charlie"; // ✓ valid — mutation is allowed
Use const by default. Switch to let only when you know the value needs to change. Avoid var entirely in modern JavaScript.
JavaScript has 7 primitive types and one complex type (Object). Primitives are immutable and stored by value; objects are stored by reference.
// Primitives const str = "Hello"; // String const num = 42; // Number const bigint = 9007199254740992n; // BigInt const bool = true; // Boolean const nothing = null; // Null const undef = undefined; // Undefined const sym = Symbol("id"); // Symbol (unique) // Complex type const obj = { x: 1, y: 2 }; // Object const arr = [1, 2, 3]; // Array (also an Object) const func = () => {}; // Function (also an Object) // typeof operator console.log(typeof "hello"); // "string" console.log(typeof 42); // "number" console.log(typeof null); // "object" ← known JS quirk!
Operators perform operations on values. The critical distinction in JavaScript is between loose equality (==) and strict equality (===).
// Arithmetic 5 + 3 // 8 10 % 3 // 1 (modulo / remainder) 2 ** 8 // 256 (exponentiation) // Comparison — ALWAYS use === and !== 5 === 5 // true (strict: same value AND type) 5 == "5" // true (loose: coerces types — AVOID) 5 !== "5" // true (strict not-equal) // Logical true && false // false (AND) true || false // true (OR) !true // false (NOT) // Nullish coalescing (ES2020) const val = null ?? "default"; // "default" // Optional chaining (ES2020) const city = user?.address?.city; // undefined (no error)
The == operator performs type coercion, which leads to surprising results like [] == false being true. Always use ===.
Day 1 Complete!
You know JavaScript's building blocks. Tomorrow: control flow, loops, and functions.
Control Flow & Functions
Make decisions with conditionals, iterate with loops, and write reusable logic using the many forms of JavaScript functions.
Conditionals execute code based on whether a condition is truthy or falsy. JavaScript has 6 falsy values: false, 0, "", null, undefined, NaN.
// if / else if / else const score = 85; if (score >= 90) { console.log("A"); } else if (score >= 80) { console.log("B"); // ← runs this } else { console.log("C or below"); } // Ternary operator — concise one-liner const status = score >= 60 ? "pass" : "fail"; // switch — best for many discrete cases switch (status) { case "pass": console.log("Congratulations!"); break; case "fail": console.log("Try again."); break; default: console.log("Unknown status"); }
Loops repeat code. Choose the right loop for the job: for when you know the count, while for condition-based repetition, and array methods (forEach, map) for cleaner iteration.
// Classic for loop for (let i = 0; i < 5; i++) { console.log(i); // 0 1 2 3 4 } // while loop let count = 3; while (count > 0) { console.log(count--); // 3 2 1 } // for...of — iterates VALUES (arrays, strings) const fruits = ["apple", "banana", "cherry"]; for (const fruit of fruits) { console.log(fruit); } // for...in — iterates KEYS (objects) const person = { name: "Alice", age: 30 }; for (const key in person) { console.log(key, person[key]); // name Alice, age 30 } // break and continue for (let i = 0; i < 10; i++) { if (i === 3) continue; // skip 3 if (i === 6) break; // stop at 6 console.log(i); // 0 1 2 4 5 }
Functions are first-class citizens in JavaScript — they can be stored in variables, passed as arguments, and returned from other functions. There are several ways to define them.
// 1. Function Declaration — hoisted (can be called before defined) function greet(name) { return `Hello, ${name}!`; } // 2. Function Expression — not hoisted const add = function(a, b) { return a + b; }; // 3. Arrow Function — concise, no own 'this' const multiply = (a, b) => a * b; // implicit return const square = x => x ** 2; // single param — no parens needed // Default parameters (ES6) const greetUser = (name = "World") => `Hello, ${name}!`; greetUser(); // "Hello, World!" greetUser("Alice"); // "Hello, Alice!" // Rest parameters — gather remaining args into array const sum = (...nums) => nums.reduce((a, b) => a + b, 0); sum(1, 2, 3, 4); // 10
Scope defines where a variable is accessible. Hoisting is JavaScript's behavior of moving declarations to the top of their scope during compilation — but only for var and function declarations.
// Function declarations are hoisted sayHi(); // "Hi!" — works even before definition function sayHi() { console.log("Hi!"); } // let/const have Temporal Dead Zone (TDZ) // console.log(x); → ReferenceError let x = 5; // Block scope with let/const { let inner = "only here"; console.log(inner); // "only here" } // console.log(inner); → ReferenceError // Closures — inner function remembers outer scope function makeCounter() { let count = 0; return () => ++count; } const counter = makeCounter(); counter(); // 1 counter(); // 2
Closures enable data privacy, function factories, and memoization. They're the foundation of many design patterns in JavaScript.
Day 2 Complete!
You can now control program flow and build reusable functions. Tomorrow: arrays, objects, and destructuring.
Arrays, Objects & Destructuring
Master JavaScript's core data structures and learn modern ES6+ features like destructuring, spread, and rest that make working with data elegant.
Arrays are ordered, zero-indexed collections. They can hold any mix of types and grow or shrink dynamically.
const nums = [10, 20, 30, 40, 50]; // Access by index (0-based) nums[0]; // 10 nums[4]; // 50 nums.at(-1); // 50 ← ES2022 negative indexing // Mutation methods nums.push(60); // add to end → [10,20,30,40,50,60] nums.pop(); // remove from end nums.unshift(0); // add to start nums.shift(); // remove from start nums.splice(1, 2); // remove 2 elements at index 1 // Non-mutating nums.slice(1, 3); // [20, 30] — returns new array nums.concat([70]); // new array with 70 appended [...nums, 70]; // same with spread
These are the most important array methods in professional JavaScript. They take a callback function and return a new array or value without mutating the original.
const products = [ { name: "Laptop", price: 999, inStock: true }, { name: "Phone", price: 699, inStock: false }, { name: "Tablet", price: 499, inStock: true }, { name: "Monitor", price: 399, inStock: true }, ]; // .map() — transform each element const names = products.map(p => p.name); // ["Laptop", "Phone", "Tablet", "Monitor"] // .filter() — keep elements that pass a test const available = products.filter(p => p.inStock); // Laptop, Tablet, Monitor // .reduce() — accumulate to a single value const total = products.reduce((sum, p) => sum + p.price, 0); // 2596 // .find() — first match const cheapest = products.find(p => p.price < 500); // { name: "Tablet", ... } // .some() / .every() products.some(p => p.price > 900); // true products.every(p => p.price > 100); // true // Chaining methods const result = products .filter(p => p.inStock) .map(p => ({ ...p, discounted: p.price * 0.9 })) .sort((a, b) => a.price - b.price);
Objects store key-value pairs. Keys are strings (or Symbols); values can be anything. Objects are the backbone of JavaScript data modeling.
const user = { name: "Alice", age: 30, address: { city: "London", zip: "EC1A" }, greet() { return `Hi, I'm ${this.name}`; } }; // Property access user.name; // "Alice" (dot notation) user["age"]; // 30 (bracket notation) // Computed property names const key = "status"; const obj = { [key]: "active" }; // { status: "active" } // Useful Object methods Object.keys(user); // ["name", "age", "address", "greet"] Object.values(user); // ["Alice", 30, {...}, fn] Object.entries(user); // [["name","Alice"], ["age",30], ...] Object.assign({}, user, { age: 31 }); // shallow clone + override Object.freeze(user); // makes object immutable
ES6 destructuring lets you unpack values from arrays or objects into variables concisely. Spread (...) copies items, rest (...) collects them.
// Array destructuring const [first, second, ...rest] = [1, 2, 3, 4]; // first=1, second=2, rest=[3,4] // Object destructuring const { name, age, address: { city } } = user; // name="Alice", age=30, city="London" // Default values in destructuring const { role = "guest" } = user; // "guest" (not in user) // Renaming const { name: userName } = user; // userName = "Alice" // Spread — copy / merge const cloned = { ...user }; const updated = { ...user, age: 31 }; // immutable update pattern const merged = { ...defaults, ...overrides }; // Function destructuring in parameters const display = ({ name, age = 18 }) => console.log(`${name} is ${age}`);
Day 3 Complete!
Arrays and objects mastered. Tomorrow: strings, numbers, and working with dates.
Strings, Numbers & Dates
Work confidently with JavaScript's built-in String and Number methods, template literals, regular expressions, and the Date API.
Strings are immutable in JavaScript. Every string method returns a new string. Template literals (backticks) enable multi-line strings and interpolation.
const str = " Hello, JavaScript World! "; // Common methods str.trim(); // "Hello, JavaScript World!" str.toLowerCase(); // " hello, javascript world! " str.toUpperCase(); // " HELLO, JAVASCRIPT WORLD! " str.includes("JavaScript"); // true str.startsWith(" Hello"); // true str.indexOf("World"); // 18 str.slice(2, 7); // "Hello" str.replace("JavaScript", "JS"); // replaces first match str.replaceAll("l", "L"); // replaces all str.split(", "); // [" Hello", "JavaScript World! "] str.padStart(30, "*"); // pads to length 30 str.repeat(2); // doubles the string // Template literals const name = "Alice"; const greeting = ` Hello, ${name}! Today is ${new Date().toDateString()}. 2 + 2 = ${2 + 2} `;
// Number methods const n = 3.14159; n.toFixed(2); // "3.14" (returns string) n.toPrecision(4); // "3.142" Number("42"); // 42 parseInt("42px"); // 42 parseFloat("3.14em"); // 3.14 isNaN("hello"); // true isFinite(Infinity); // false // Math object Math.round(4.6); // 5 Math.floor(4.9); // 4 Math.ceil(4.1); // 5 Math.abs(-5); // 5 Math.max(1,5,3); // 5 Math.min(1,5,3); // 1 Math.sqrt(16); // 4 Math.pow(2, 10); // 1024 Math.random(); // random float [0, 1) // Random int between min and max (inclusive) const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
0.1 + 0.2 !== 0.3 in JavaScript (and most languages) due to IEEE 754 floating point. Use (0.1 + 0.2).toFixed(1) or multiply to integers for financial calculations.
Regular expressions (RegExp) are patterns for matching, searching, and replacing text.
// Create a regex const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/; const phoneRegex = new RegExp('^\\d{10}$'); // test() — returns boolean emailRegex.test("user@example.com"); // true // match() — returns matches "Hello World".match(/\b\w+\b/g); // ["Hello", "World"] // replace() with regex "foo bar baz".replace(/\s/g, "-"); // "foo-bar-baz" // Named groups (ES2018) const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const { groups: { year, month, day } } = "2024-03-15".match(dateRegex);
Day 4 Complete!
String and number mastery achieved! Tomorrow: OOP with classes, prototypes, and inheritance.
Object-Oriented Programming & Classes
Understand JavaScript's prototype-based inheritance, master ES6 classes, encapsulation, and design scalable object hierarchies.
Every JavaScript object has an internal link to another object called its prototype. When a property isn't found on an object, JS looks up the chain — this is called prototypal inheritance.
// All arrays inherit from Array.prototype const arr = [1, 2, 3]; Object.getPrototypeOf(arr) === Array.prototype; // true // Chain: arr → Array.prototype → Object.prototype → null // Adding to prototype (don't do this for built-ins in production!) Array.prototype.first = function() { return this[0]; }; [10, 20].first(); // 10 // hasOwnProperty — check own vs inherited const obj = { a: 1 }; obj.hasOwnProperty("a"); // true obj.hasOwnProperty("toString"); // false (inherited)
Classes are syntactic sugar over prototypes — they make OOP patterns cleaner and more readable. Under the hood, it's still prototype-based inheritance.
class Animal { // Private field (ES2022) #energy = 100; constructor(name, species) { this.name = name; this.species = species; } // Instance method speak() { return `${this.name} makes a sound.`; } // Getter get energy() { return this.#energy; } // Setter set energy(val) { if (val < 0) throw new Error("Energy can't be negative"); this.#energy = val; } // Static method — called on class, not instance static compare(a, b) { return a.name.localeCompare(b.name); } } // Inheritance with extends class Dog extends Animal { constructor(name, breed) { super(name, "Canis lupus"); // call parent constructor this.breed = breed; } // Override parent method speak() { return `${this.name} barks!`; } } const rex = new Dog("Rex", "Labrador"); rex.speak(); // "Rex barks!" rex instanceof Dog; // true rex instanceof Animal; // true
this refers to the object executing the current function. Its value depends on how the function is called, not where it's defined.
// 'this' in methods → the object const obj = { name: "Alice", greet() { return this.name; } // "Alice" }; // Arrow functions inherit 'this' from surrounding scope class Timer { constructor() { this.count = 0; } start() { // Arrow function captures 'this' from constructor context setInterval(() => this.count++, 1000); } } // Explicit binding: call, apply, bind function introduce(greeting) { return `${greeting}, I'm ${this.name}`; } const person = { name: "Bob" }; introduce.call(person, "Hello"); // invoke with 'this'=person introduce.apply(person, ["Hi"]); // same but args as array const bound = introduce.bind(person); // new function with fixed 'this'
Day 5 Complete!
Halfway through! Tomorrow: the DOM, events, and building interactive UIs.
The DOM & Events
The Document Object Model is JavaScript's interface to the browser. Learn to query elements, manipulate the DOM, and handle user interactions with events.
// Modern query methods (returns live/static collections) const btn = document.querySelector("#submit-btn"); const inputs = document.querySelectorAll("input[type='text']"); const header = document.querySelector(".header h1"); // querySelectorAll returns a NodeList — convert to array for methods const items = [...document.querySelectorAll(".item")]; items.forEach(item => console.log(item.textContent)); // Traversal btn.parentElement; // parent node btn.children; // HTMLCollection of children btn.nextElementSibling; // next sibling btn.closest(".form"); // nearest ancestor matching selector
const el = document.querySelector("#box"); // Content el.textContent = "Safe text"; // no HTML parsing el.innerHTML = "<b>Bold</b>"; // parsed as HTML (beware XSS) // Attributes el.setAttribute("data-id", "42"); el.getAttribute("data-id"); // "42" el.removeAttribute("disabled"); // Classes el.classList.add("active"); el.classList.remove("hidden"); el.classList.toggle("open"); el.classList.contains("active"); // true // Styles el.style.backgroundColor = "#ff6b35"; el.style.transform = "scale(1.1)"; // Creating & inserting elements const newDiv = document.createElement("div"); newDiv.className = "card"; newDiv.textContent = "Hello!"; document.body.appendChild(newDiv); document.body.insertBefore(newDiv, el); el.insertAdjacentHTML("beforeend", "<span>+</span>");
Events are the core of interactive JavaScript. Use addEventListener instead of inline handlers for clean, maintainable code.
const btn = document.querySelector("#btn"); // Add event listener btn.addEventListener("click", (event) => { event.preventDefault(); // stop default behavior event.stopPropagation(); // stop bubbling console.log(event.target); // element clicked console.log(event.currentTarget); // listener's element }); // Event delegation — handle children via parent document.querySelector("#list") .addEventListener("click", (e) => { if (e.target.matches("li")) { console.log("Clicked:", e.target.textContent); } }); // Common events el.addEventListener("mouseover", handler); el.addEventListener("keydown", e => console.log(e.key)); el.addEventListener("submit", e => e.preventDefault()); el.addEventListener("input", e => console.log(e.target.value)); // Remove listener btn.removeEventListener("click", handler);
Day 6 Complete!
You can now build interactive web pages! Tomorrow: async JS — promises, fetch, and async/await.
Asynchronous JavaScript
JavaScript is single-threaded but handles async operations through the event loop. Master callbacks, Promises, async/await, and the Fetch API.
JavaScript has a call stack (sync code), a task queue (macro-tasks like setTimeout), and a microtask queue (Promises). The event loop processes microtasks before macro-tasks.
console.log("1 — sync"); setTimeout(() => console.log("4 — macro-task"), 0); Promise.resolve() .then(() => console.log("3 — microtask")); console.log("2 — sync"); // Output: 1, 2, 3, 4
A Promise represents a value that may be available now, in the future, or never. It has three states: pending, fulfilled, or rejected.
// Creating a Promise const fetchUser = (id) => new Promise((resolve, reject) => { setTimeout(() => { if (id > 0) { resolve({ id, name: "Alice" }); } else { reject(new Error("Invalid ID")); } }, 1000); }); // Consuming with .then/.catch/.finally fetchUser(1) .then(user => console.log(user.name)) // "Alice" .catch(err => console.error(err)) .finally(() => console.log("Done")); // Promise combinators Promise.all([fetchUser(1), fetchUser(2)]) .then(([u1, u2]) => console.log(u1, u2)); // Fails if ANY promise rejects Promise.allSettled([fetchUser(1), fetchUser(-1)]) .then(results => results.forEach(r => console.log(r.status))); // "fulfilled", "rejected" — never throws Promise.race([fetchUser(1), fetchUser(2)]); // resolves/rejects with whoever is first
async/await is syntactic sugar over Promises that makes async code read like synchronous code. An async function always returns a Promise.
// async function — implicitly returns a Promise async function loadUserData(userId) { try { const user = await fetchUser(userId); const posts = await fetchPosts(user.id); return { user, posts }; } catch (error) { console.error("Failed:", error.message); throw error; } } // Parallel execution (don't await sequentially when independent!) async function loadAll() { // BAD — sequential (2 seconds total) const a = await fetch1(); // 1s const b = await fetch2(); // 1s // GOOD — parallel (1 second total) const [a2, b2] = await Promise.all([fetch1(), fetch2()]); }
The fetch function is the modern way to make HTTP requests in the browser. It returns a Promise.
// GET request async function getUser(id) { const response = await fetch(`https://api.example.com/users/${id}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } // POST request async function createUser(userData) { const response = await fetch("https://api.example.com/users", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify(userData) }); return response.json(); }
Day 7 Complete!
Async JavaScript mastered! Tomorrow: ES6+ modules, error handling, and modern tooling.
Modules, Error Handling & Tooling
Structure large applications with ES Modules, write bulletproof code with robust error handling, and understand the modern JavaScript toolchain.
Modules let you split code into separate files. Each module has its own scope — nothing is global unless explicitly exported.
// Named exports export const add = (a, b) => a + b; export const subtract = (a, b) => a - b; export const PI = 3.14159; // Default export — one per file export default function multiply(a, b) { return a * b; }
// Import named exports import { add, PI } from "./math.js"; // Import default export import multiply from "./math.js"; // Import all as namespace import * as Math2 from "./math.js"; Math2.add(1, 2); // Dynamic import — loads only when needed const { add: add2 } = await import("./math.js"); // Great for code splitting / lazy loading
Production code needs comprehensive error handling. Use try/catch/finally, create custom error types, and always handle Promise rejections.
// Custom Error classes class ValidationError extends Error { constructor(message, field) { super(message); this.name = "ValidationError"; this.field = field; } } class NetworkError extends Error { constructor(message, statusCode) { super(message); this.name = "NetworkError"; this.statusCode = statusCode; } } // Catching specific error types try { const data = await loadData(); validate(data); } catch (err) { if (err instanceof ValidationError) { showFieldError(err.field, err.message); } else if (err instanceof NetworkError) { showNetworkError(err.statusCode); } else { console.error("Unexpected error:", err); throw err; // re-throw unknown errors } } finally { hideLoadingSpinner(); // always runs } // Unhandled Promise rejection handler window.addEventListener("unhandledrejection", (e) => { console.error("Unhandled:", e.reason); });
Package managers. Install libraries, manage dependencies, and run scripts defined in package.json.
Build tools & bundlers. Bundle modules, transform code, enable HMR, and optimise for production.
Transpilers. Convert modern JS to browser-compatible versions. SWC is significantly faster (Rust-based).
Linting (catch errors & enforce rules) and formatting (consistent code style). Essential in any project.
A typed superset of JS that compiles to JavaScript. Adds static types, interfaces, and better tooling.
Testing frameworks. Write unit tests, integration tests, and mocks to ensure code correctness.
Day 8 Complete!
Your code is now production-ready. Tomorrow: functional programming patterns and advanced concepts.
Functional Programming & Advanced Patterns
Embrace immutability, pure functions, and composition. Understand closures deeply, learn iterators and generators, and explore proxy and reflection.
Functional programming (FP) focuses on pure functions, immutability, and function composition. FP code is predictable and easy to test.
// Pure function — same input always = same output, no side effects const double = x => x * 2; // ✓ pure // Immutable updates (never mutate, always return new) const addItem = (list, item) => [...list, item]; const updateUser = (user, updates) => ({ ...user, ...updates }); // Higher-order functions (functions that take/return functions) const multiplyBy = factor => n => n * factor; const triple = multiplyBy(3); const quadruple = multiplyBy(4); // Currying — transform f(a,b,c) into f(a)(b)(c) const curry = (fn) => { return function curried(...args) { if (args.length >= fn.length) return fn(...args); return (...more) => curried(...args, ...more); }; }; const add = curry((a, b, c) => a + b + c); add(1)(2)(3); // 6 add(1, 2)(3); // 6 // Composition — pipe functions left to right const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x); const process = pipe( x => x * 2, x => x + 1, x => x ** 2 ); process(3); // ((3*2)+1)^2 = 49
// Memoization — cache results of expensive calls const memoize = (fn) => { const cache = new Map(); return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn(...args); cache.set(key, result); return result; }; }; const expensiveFib = memoize((n) => n <= 1 ? n : expensiveFib(n-1) + expensiveFib(n-2) );
Generators are functions that can pause and resume execution using yield. They implement the iterator protocol.
// Generator function — denoted by function* function* range(start, end, step = 1) { for (let i = start; i < end; i += step) { yield i; } } for (const n of range(0, 10, 2)) { console.log(n); // 0 2 4 6 8 } // Infinite generator function* ids() { let id = 1; while (true) yield id++; } const gen = ids(); gen.next().value; // 1 gen.next().value; // 2 // Async generators (ES2018) async function* streamData() { for (const page of [1, 2, 3]) { yield await fetchPage(page); } } for await (const page of streamData()) { process(page); }
// Symbol — unique, non-enumerable property keys const id = Symbol("id"); const user = { [id]: 42, name: "Alice" }; Object.keys(user); // ["name"] — symbol not listed // WeakMap — gc-friendly private data storage const privateData = new WeakMap(); class Person { constructor(name) { privateData.set(this, { secret: "hidden" }); this.name = name; } } // Proxy — intercept object operations const handler = { get(target, key) { console.log(`Getting: ${String(key)}`); return Reflect.get(target, key); }, set(target, key, value) { if (typeof value !== "number") throw new TypeError("Numbers only!"); return Reflect.set(target, key, value); } }; const nums = new Proxy({}, handler); nums.x = 10; // ✓ nums.y = "hi"; // TypeError!
Day 9 Complete!
Advanced patterns unlocked! Final day tomorrow: design patterns, performance, and what's next.
Design Patterns, Performance & What's Next
Cap your JavaScript journey with professional design patterns, browser performance techniques, security best practices, and a roadmap for continued mastery.
Design patterns are reusable solutions to common software design problems. Here are the most relevant ones for JavaScript.
// Singleton — only one instance ever created class AppConfig { static #instance = null; constructor() { if (AppConfig.#instance) return AppConfig.#instance; this.theme = "dark"; this.lang = "en"; AppConfig.#instance = this; } } const c1 = new AppConfig(); const c2 = new AppConfig(); c1 === c2; // true
// Observer — pub/sub event system class EventEmitter { #events = {}; on(event, listener) { (this.#events[event] ||= []).push(listener); return this; } off(event, listener) { this.#events[event] = this.#events[event] ?.filter(l => l !== listener); return this; } emit(event, ...args) { this.#events[event]?.forEach(l => l(...args)); } once(event, listener) { const wrapper = (...args) => { listener(...args); this.off(event, wrapper); }; return this.on(event, wrapper); } } const emitter = new EventEmitter(); emitter.on("data", payload => console.log(payload)); emitter.emit("data", { userId: 1 });
// Debounce — delay execution until after activity stops const debounce = (fn, delay) => { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; }; // Throttle — execute at most once per interval const throttle = (fn, limit) => { let inThrottle; return (...args) => { if (!inThrottle) { fn(...args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }; // Usage window.addEventListener("scroll", throttle(updateNav, 100)); searchInput.addEventListener("input", debounce(searchAPI, 300)); // requestAnimationFrame for smooth animations function animate(timestamp) { // update position based on timestamp requestAnimationFrame(animate); } requestAnimationFrame(animate); // Virtual DOM concept — batch DOM updates // (used by React, Vue under the hood) const fragment = document.createDocumentFragment(); items.forEach(item => { const li = document.createElement("li"); li.textContent = item; fragment.appendChild(li); // no DOM reflow }); list.appendChild(fragment); // one reflow
Never use innerHTML with user data. Use textContent or sanitize with DOMPurify.
Use SameSite cookies, CSRF tokens, and validate origins with Origin headers.
Use CSP headers to prevent script injection attacks: Content-Security-Policy: default-src 'self'.
Validate ALL user input — both client-side (UX) and server-side (security). Never trust the client.
Add static types to JavaScript. Industry standard for large applications. 3–5 days to get productive.
Component-based UI frameworks. React is the most in-demand. Build real projects immediately.
Run JavaScript on the server. Build REST APIs, real-time apps, and CLIs. Pair with Express or Fastify.
Learn Jest or Vitest for unit tests. Playwright or Cypress for E2E. Testing = professional JS.
LeetCode in JS. Understanding DSA makes you a better engineer and clears technical interviews.
A todo app, a weather app, a full-stack clone. Real projects teach more than any tutorial.