JavaScript Syllabus
JavaScript Syllabus
The Rules
• Follow the topic order within each unit. The sequence is intentional — later topics
depend on earlier ones.
• For every topic: read first, experiment in the console/terminal, then tackle the 'Under the
Hood' section.
• Do not move to the next unit until you finish the project. The project exposes what you
think you understood but didn't.
• You write every line of code yourself. No copying. No AI-generated code.
• When stuck — bring your code here. We review it, we find the gaps, you go fix them.
• You can skip topics you already know solid. Just say so and we'll do a quick test to
confirm.
Reading Priority
• PRIMARY — [Link]: This is the best JS resource on the internet. Treat it like a
textbook.
• REFERENCE — MDN ([Link]): Use this when you want the exact,
authoritative definition of something.
• DEEP DIVE — ECMAScript Spec ([Link]/ecma262): The actual law of the language.
You will visit this for Under the Hood sections.
• ENGINE INTERNALS — V8 Blog ([Link]/blog): Written by the people who build the JS
engine. Required for true depth.
UNIT 1
Operators
5 Mostly familiar, but == vs === and nullish coalescing are new
Control Flow
7 Loops and conditionals. Mostly familiar, one new: for...of vs for...in
What it is:
JS was originally built to run inside web browsers. Unlike Java where you compile to bytecode and the JVM
runs it, JS is interpreted (or JIT-compiled) by an engine built into the browser — V8 in Chrome, SpiderMonkey
in Firefox. Today it also runs on the server via [Link], which uses the V8 engine outside the browser.
Understanding this environment changes how you think about everything.
What to study:
• What JavaScript is and what problems it was built to solve
• How the browser loads and runs a JS file
• The <script> tag — where to put it, defer vs async attributes, and why position matters
• JS engines — V8, SpiderMonkey, JavaScriptCore — what they are
• What JIT (Just-In-Time) compilation means — JS is not purely interpreted
• [Link] — how it takes V8 out of the browser and runs JS on a server
• How to run JS: browser console, [Link] terminal, VS Code
• ECMAScript vs JavaScript — what the spec is and why it matters
• JS versions: ES5, ES6 (2015), ES2017, ES2020... what ES6 was and why it was a revolution
What it is:
In Java, every variable has a type declared at compile time. In JS, variables have no declared type — they
hold whatever you put in them. But HOW you declare a variable (var vs let vs const) has massive implications
for scoping and hoisting. This is not just syntax — it determines how the JS engine allocates and looks up
memory.
What to study:
• var — function-scoped, hoisted to the top of its function, can be re-declared
• let — block-scoped (like Java), NOT hoisted in the same way as var
• const — block-scoped, must be initialized, cannot be reassigned (but object properties CAN be
mutated)
• Why var was the only option before ES6 and what problems it caused
• The Temporal Dead Zone (TDZ) — the zone where let/const exist but cannot be accessed
• Hoisting — what it means, how var is hoisted differently from let/const
• Variable naming rules and conventions in JS
• When to use const vs let — the rule: default to const, use let only when you need to reassign
📖 Read On: [Link]/variables · [Link]/var · MDN: var · MDN: let · MDN: const
What it is:
JS has 8 data types. 7 are primitive (immutable, stored by value) and 1 is Object (stored by reference). This
split — primitives vs reference types — is the same concept you know from Java (int vs Integer) but in JS it
applies everywhere. The typeof operator tells you what type something is — but it has one famous bug that
has lived in the language since 1995.
What to study:
• The 7 primitive types: string, number, boolean, null, undefined, symbol, bigint
• The object type: objects, arrays, functions — all are 'object' under the hood
• typeof operator — what it returns for each type
• The famous typeof null === 'object' bug — why it exists and has never been fixed
• null vs undefined — they are not the same. null is intentional absence. undefined is 'not yet assigned'
• NaN — Not a Number. Why NaN is typeof 'number'. Why NaN !== NaN
• Infinity and -Infinity in JS numbers
• Primitives are immutable — what that actually means
• How strings behave like objects (you can call .length, .toUpperCase()) — wrapper objects
• BigInt — when to use it, what it solves with regular number precision
• Symbol — a unique, non-string key type. Brief intro here, deep dive in Unit 3
What it is:
Type coercion is when JS automatically converts one type to another without you asking. It happens during
operations like addition, comparison, and logical evaluations. This is one of the most infamous features of JS
— it causes confusing bugs if you don't understand it, and it makes total sense once you do. Explicit
conversion is when YOU do it manually.
What to study:
• Implicit coercion — when JS converts types automatically during +, ==, if(), etc.
• The + operator with strings — '5' + 3 = '53' (concatenation beats addition)
• The - * / operators — these try to convert to numbers
• Explicit conversion: Number(), String(), Boolean()
• parseInt() and parseFloat() — how they parse strings to numbers
• Boolean conversion rules — what becomes true and what becomes false
• == (abstract equality) — the comparison algorithm with type coercion
• === (strict equality) — no coercion, same type required
• Why [] == false is true — walk through the actual coercion steps
• Why {} + [] = 0 in some contexts but '[object Object]' in others
TOPIC 5 — Operators
What it is:
Most operators you know from Java. The interesting ones in JS are the ones that don't exist in Java: the
nullish coalescing operator (??) and optional chaining (?.). These were added to handle the reality that JS
code is full of null and undefined values, and crashing on them was a constant problem.
What to study:
• Arithmetic operators: +, -, *, /, %, ** (exponentiation — new in ES2016)
• Assignment operators: =, +=, -=, *=, /=, ??=, ||=, &&=
• Comparison operators: ==, ===, !=, !==, <, >, <=, >=
• Logical operators: && (AND), || (OR), ! (NOT)
• Short-circuit evaluation — && returns left if falsy, || returns left if truthy
• Nullish coalescing: ?? — returns right side only if left is null or undefined (not just falsy)
• Optional chaining: ?. — access nested properties without crashing if something is null
• typeof operator
• instanceof operator — checks prototype chain (you'll understand this fully in Unit 5)
• Comma operator — rarely used but exists
• Bitwise operators — brief, you know these from C++
• Operator precedence — when to use parentheses
What it is:
In Java, only a boolean can be used in a condition. In JS, ANYTHING can be used in a condition. JS will
convert it to boolean. The values that become false are called 'falsy' — there are exactly 6 of them. Everything
else is 'truthy'. This concept appears in every single unit that follows. Do not skip this.
What to study:
• The 6 falsy values: false, 0, '' (empty string), null, undefined, NaN
• Everything else is truthy — including empty arrays [], empty objects {}, and the string '0'
• Why [] is truthy — this surprises most people coming from other languages
• Using truthy/falsy in if statements and ternary operators
• Double negation !! — converting any value to its boolean equivalent
• Boolean() function vs !! — both convert to boolean, same result
• Common patterns: if (user) {...} instead of if (user !== null && user !== undefined)
• Where truthy/falsy catches you out — the 0 problem: if (count) fails when count is legitimately 0
What it is:
Control flow in JS is mostly the same as Java — if/else, switch, for, while. The main additions are for...of and
for...in, which are very different from each other and from Java's enhanced for loop. You also need to
understand labeled statements which exist but are rarely used.
What to study:
• if / else if / else — same as Java
• Ternary operator: condition ? valueIfTrue : valueIfFalse
• switch statement — same as Java, with the same fall-through behavior
• while loop and do...while loop
• for loop — same as Java
• for...in — iterates over object KEYS (property names). Not for arrays.
• for...of — iterates over iterable VALUES (arrays, strings, Maps, Sets). This is the one you'll use most.
• Why you should NOT use for...in on arrays — it iterates prototype properties too
• break and continue — same as Java
• Labeled statements — rare, but exist: outerLoop: for(...) { break outerLoop; }
UNIT 2
Arrow Functions
3 The ES6 way — shorter syntax, different behavior for 'this'
Closures
5 The most important concept in JS. Take your time here.
What it is:
There are two main ways to create a function in JS. A function declaration uses the 'function' keyword as a
statement. A function expression assigns a function to a variable. They look similar but behave very differently
due to hoisting.
What to study:
• Function declaration: function greet() {} — hoisted completely, can be called before it's defined
• Function expression: const greet = function() {} — NOT hoisted, treated like a variable
• Named function expressions — assigning a function with its own name to a variable
• Why function declarations are hoisted but function expressions are not
• Anonymous functions — functions with no name (common in function expressions)
• First-class functions — what it means that functions are values in JS
• Storing functions in variables, arrays, and object properties
What it is:
Parameters are the variable names in the function definition. Arguments are the actual values you pass when
calling it. JS is very lenient here — you can pass more or fewer arguments than there are parameters and it
won't crash. This is different from Java's strict signature matching.
What to study:
• Parameters vs arguments — the distinction
• Passing more arguments than parameters — extra arguments are ignored
• Passing fewer arguments than parameters — missing ones are undefined
• Default parameters: function greet(name = 'World') — ES6 addition
• The arguments object — an array-like object available in regular functions (NOT arrow functions)
• Rest parameters: function sum(...nums) — collects all extra args into a real array
• Spread syntax: [Link](...arr) — expands an array into individual arguments
• The difference between rest (in function definition) and spread (in function call)
• Return values — a function without return returns undefined
• Multiple return values — JS doesn't support them, but you can return an array or object
What it is:
Arrow functions are a shorter way to write functions introduced in ES6. But they are NOT just shorthand —
they have a fundamentally different behavior around 'this'. Understanding when to use them and when NOT to
use them is critical. Most JS you read today will be full of arrow functions.
What to study:
• Basic syntax: const fn = (x) => x * 2
• Implicit return — single expression: no curly braces needed, value is returned automatically
• Explicit return — multi-line: needs curly braces and return keyword
• Parentheses rules — one param: can omit parens. Zero or multiple: parens required
• Returning an object literal — must wrap in parens: () => ({ key: value })
• Arrow functions do NOT have their own 'this' — they inherit from enclosing scope
• Arrow functions do NOT have arguments object
• Arrow functions cannot be used as constructors (cannot use new)
• Arrow functions do NOT have prototype property
• When NOT to use arrow functions: object methods, event handlers where you need 'this', constructors
What it is:
Scope determines where a variable is accessible. JS has three kinds of scope: global, function, and block.
When JS looks for a variable, it starts in the current scope and walks up through outer scopes until it finds it —
this is the scope chain. If it reaches global scope and still can't find it, you get a ReferenceError.
What to study:
• Global scope — variables declared outside any function or block
• Function scope — var declared inside a function is only visible inside that function
• Block scope — let and const declared inside {} are only visible inside that block
• The scope chain — how JS walks up through nested scopes to find a variable
• Lexical scoping — scope is determined by where code is WRITTEN, not where it's called from
• Variable shadowing — a variable in inner scope hiding one in outer scope with the same name
• The global object — window in browsers, global in [Link], globalThis everywhere
• Strict mode — what 'use strict' does to scoping and undeclared variables
TOPIC 5 — Closures
What it is:
A closure is a function that remembers the variables from its outer scope even after that outer function has
finished executing. This sounds simple but has deep implications. Closures are the basis for data privacy,
module patterns, callbacks, and much of modern JS. This is the topic where most people think they
understand it and then discover they don't.
What to study:
• What a closure is — a function + the environment it was created in
• A simple closure example: a counter function that remembers its count
• How closures persist variables in memory even after the outer function returns
• Practical uses: data privacy (private variables), factory functions, partial application
• The famous closure-in-loop bug — var in a for loop, all callbacks share the same variable
• Fixing the closure-in-loop bug with let (block scope creates a new binding per iteration)
• Module pattern using closures — simulating private state before ES6 modules
• Closures and memory — closed-over variables cannot be garbage collected
• When closures cause memory leaks — holding references to DOM nodes or large objects
What it is:
A higher-order function is a function that either takes a function as an argument or returns a function. This is
possible because functions are first-class values in JS. Callbacks are functions you pass to be called later —
this pattern is everywhere in JS, from DOM events to async operations.
What to study:
• First-class functions — what it means, why it matters
• Passing a function as an argument — the callback pattern
• Callbacks in the DOM: [Link]('click', myCallback)
• Callbacks in timers: setTimeout(myCallback, 1000)
• Callback hell — deeply nested callbacks that become unreadable
• Error-first callbacks — the [Link] pattern: callback(error, result)
• Returning functions from functions — creating function factories
• Functions that both take and return functions
• Synchronous vs asynchronous callbacks — understanding the difference
What it is:
These are the most important methods you will use as a JS developer. map transforms every element. filter
picks elements that pass a test. reduce combines all elements into a single value. They are all higher-order
functions — they take a callback. Mastering these replaces most for loops you'd write.
What to study:
• forEach() — iterate over array, no return value. When to use vs for...of
• map() — create a new array by transforming every element. Returns new array, doesn't mutate
• filter() — create a new array with only elements that pass a test. Returns new array
• reduce() — combine all elements into one value using an accumulator
• reduce() for non-obvious uses: grouping, flattening, building objects from arrays
• find() — returns first element that passes a test (not an array, the actual element)
• findIndex() — returns index of first element that passes a test
• some() — returns true if ANY element passes the test
• every() — returns true if ALL elements pass the test
• flat() — flatten nested arrays
• flatMap() — map then flatten in one step
• Chaining: [Link](...).map(...).reduce(...) — the power of returning new arrays
What it is:
In Java, 'this' always refers to the current instance of the class. In JS, 'this' is dynamic — it depends on HOW
the function is called, not where it's defined (except for arrow functions). There are exactly 4 rules that
determine what 'this' is. Memorize these rules.
What to study:
• Rule 1 — Default binding: called as a plain function → this is global object (or undefined in strict mode)
• Rule 2 — Implicit binding: called as a method [Link]() → this is obj
• Rule 3 — Explicit binding: called with .call() or .apply() → this is what you pass
• Rule 4 — new binding: called with new → this is the newly created object
• Arrow functions: no rule applies — they inherit 'this' from the enclosing lexical scope
• The implicit binding bug — losing 'this' when you store a method in a variable
• Class methods and 'this' — the same rules apply
• Event listeners and 'this' — by default, this is the element the listener is attached to
• Why this is confusing — it's determined at runtime, not at definition time
What it is:
These three methods let you manually control what 'this' refers to when calling a function. call and apply
invoke the function immediately with a specified this. bind returns a NEW function with 'this' permanently set
— it does not call immediately.
What to study:
• call(thisArg, arg1, arg2, ...) — call immediately, pass args as a list
• apply(thisArg, [arg1, arg2]) — call immediately, pass args as an array
• bind(thisArg, arg1, ...) — returns a new function with 'this' fixed. Does not call.
• Partial application with bind — pre-fill some arguments
• Common use: borrowing methods from one object to use on another
• Common use: fixing 'this' for callbacks that lose their context
• The difference: call/apply call now, bind returns a new function
What it is:
An IIFE (Immediately Invoked Function Expression) is a function that runs as soon as it is defined. Pure
functions always return the same output for the same input and have no side effects. Memoization is a
technique to cache the result of expensive function calls.
What to study:
• IIFE syntax: (function() { ... })() or (() => { ... })()
• Why IIFEs were used — creating private scope before ES6 modules
• IIFEs for avoiding global variable pollution
• Pure functions — same input always produces same output, no side effects
• Impure functions — reading from/writing to external state, random numbers, current time
• Why pure functions are easier to test and reason about
• Side effects — what they are and why they need to be managed carefully
• Memoization — caching function results to avoid recomputing expensive operations
• Building a simple memoize function using closures and a Map
UNIT 3
Destructuring
4 The modern way to pull data out of objects and arrays
JSON
9 The data format of the web
What it is:
An object in JS is a collection of key-value pairs. Keys are strings (or Symbols). Values can be anything —
primitives, other objects, functions. Objects in JS are dynamic — you can add, change, and delete properties
at any time, unlike Java where a class defines the structure upfront.
What to study:
• Object literal syntax: const obj = { key: value, key2: value2 }
• Dot notation: [Link] — clean, but only works with valid identifier names
• Bracket notation: obj['property'] — works with any string, variables as keys, dynamic access
• Adding new properties at any time: [Link] = 'value'
• Updating properties: [Link] = 'newValue'
• Deleting properties: delete [Link]
• Shorthand property names (ES6): const name = 'Ali'; const obj = { name } instead of { name: name }
• Computed property names: const key = 'id'; const obj = { [key]: 123 }
• Checking if property exists: 'key' in obj vs [Link] !== undefined (they behave differently)
• hasOwnProperty() — check if property belongs to the object itself, not prototype
• [Link]() — the modern replacement for hasOwnProperty()
• Property shorthand in methods: { greet() {} } instead of { greet: function() {} }
What it is:
Primitives are stored by value — assigning copies the value. Objects are stored by reference — assigning
copies the memory address. This means two variables can point to the same object, and changing it through
one variable affects the other. This is the source of many subtle bugs.
What to study:
• Primitive assignment copies the value — const a = 5; const b = a; — b has its own 5
• Object assignment copies the reference — const a = {}; const b = a; — both point to the same object
• Equality of objects — two objects with same properties are NOT equal: {} !== {}
• Shallow copy with spread: const copy = { ...original } — copies top-level properties
• Shallow copy with [Link](): [Link]({}, original)
• Why shallow copy is not enough for nested objects
• Deep copy with JSON: [Link]([Link](obj)) — works but has limitations (loses functions,
dates become strings)
• Deep copy with structuredClone() — modern, handles more types
• When shallow copy bites you — nested array or object still shared
• Freezing objects: [Link]() — makes object immutable (shallow freeze only)
What it is:
[Link](), [Link](), and [Link]() are the three essential methods for getting data out of an
object. [Link]() goes the other direction. Combined with array methods, these let you transform
objects in powerful ways.
What to study:
• [Link](obj) — returns array of property names
• [Link](obj) — returns array of property values
• [Link](obj) — returns array of [key, value] pairs
• [Link](entries) — turns array of [key, value] pairs back into object
• for...in loop — iterates own AND inherited enumerable properties (be careful)
• Combining: [Link](obj).map(...) to transform an object
• Merging objects: [Link]() and spread { ...obj1, ...obj2 }
• [Link]() only returns own enumerable string keys — Symbol keys excluded
• Enumerable vs non-enumerable properties — brief intro (deep dive in Unit 5)
TOPIC 4 — Destructuring
What it is:
Destructuring lets you extract values from objects and arrays into named variables in a single line. It's one of
the ES6 features you'll use constantly in MERN code. React props are always destructured. API responses
are always destructured. Learn this cold.
What to study:
• Object destructuring: const { name, age } = person
• Renaming during destructuring: const { name: firstName } = person
• Default values in destructuring: const { name = 'Anonymous' } = person
• Array destructuring: const [first, second] = array
• Skipping elements: const [, , third] = array
• Rest in destructuring: const { name, ...rest } = obj — rest holds remaining properties
• Nested destructuring: const { address: { city } } = person
• Destructuring in function parameters: function greet({ name, age }) {}
• Swapping variables: [a, b] = [b, a]
• Destructuring return values: const [min, max] = getRange()
• Default values for nested: const { address: { city = 'Unknown' } = {} } = person
What it is:
Arrays in JS are dynamic, untyped, and have a huge standard library. You need to know which methods
mutate the original array and which return a new one — this distinction matters for writing predictable code.
What to study:
• MUTATING — these change the original array:
• push() — add to end. pop() — remove from end
• unshift() — add to start. shift() — remove from start
• splice(start, deleteCount, ...items) — add/remove anywhere
• sort() — sorts IN PLACE. Warning: default sort is lexicographic, not numeric
• reverse() — reverses IN PLACE
• fill() — fill elements with a value
• NON-MUTATING — these return a new array:
• slice(start, end) — copy a portion of the array
• concat() — merge arrays. Same as spread: [...arr1, ...arr2]
• [Link]() — create array from iterables, array-like objects, or with a mapping function
• [Link]() — create array from arguments
• flat(depth) — flatten nested arrays
• flatMap() — map then flat(1)
• SEARCHING:
• indexOf() / lastIndexOf() — find by value using ===
• includes() — check if value exists
• find() — find first element by condition (returns element)
• findIndex() — find index of first element by condition
• findLast() / findLastIndex() — search from the end (ES2023)
• at() — access by index, supports negative: [Link](-1) is last element
🔩 Under the Hood — Go here after the surface clicks.
– Arrays in JS are NOT true arrays — they are objects with numeric string keys and a length
property
– How V8 optimizes arrays — dense arrays (all elements filled) use real C++ arrays internally.
Sparse arrays (holes) fall back to hash maps
– Why sort() is problematic without a comparator — the spec doesn't mandate a specific algorithm,
but V8 uses TimSort
What it is:
Strings in JS are immutable primitive values. Every method that appears to modify a string actually returns a
new string. You'll use string methods constantly — parsing user input, manipulating data, building output.
What to study:
• String length: [Link]
• Accessing characters: str[0] and [Link](-1)
• Case: toUpperCase(), toLowerCase()
• Searching: indexOf(), lastIndexOf(), includes(), startsWith(), endsWith()
• Extracting: slice(start, end) — supports negative. substring(start, end) — does not
• Splitting: split(separator) — returns array
• Trimming: trim(), trimStart(), trimEnd()
• Replacing: replace(search, replacement), replaceAll()
• replace() with regex: [Link](/pattern/g, replacement)
• Padding: padStart(length, padStr), padEnd(length, padStr)
• Repeating: [Link](n)
• Template literals: `Hello ${name}` — backtick syntax
• Template literals with expressions: `${2 + 2}` = '4'
• Multi-line strings with template literals
• [Link]() — raw template strings, backslashes not processed
What it is:
Map is like an object but keys can be ANY type (not just strings). Set is a collection of unique values. Both
have better performance characteristics than plain objects/arrays for certain operations and have cleaner
iteration APIs.
What to study:
• Map vs Object — when to use each. Map if: keys are non-strings, key insertion order matters explicitly,
frequent add/remove
• Creating a Map: new Map(), new Map([[key, val], ...])
• Map methods: set(key, val), get(key), has(key), delete(key), clear()
• [Link] — the size property (not .length)
• Iterating Maps: for...of with [Link](), [Link](), [Link]()
• Converting Map to array and back
• Set — stores unique values, any type
• Creating a Set: new Set(), new Set([1,2,3,2,1]) → {1,2,3}
• Set methods: add(val), has(val), delete(val), clear()
• Classic use: remove duplicates from array: [...new Set(arr)]
• Set operations — union, intersection, difference (must implement manually)
• WeakMap — like Map but keys must be objects, keys are weakly referenced (allows GC)
• WeakSet — like Set but values must be objects
• When to use WeakMap — storing metadata about objects without preventing GC
TOPIC 8 — JSON
What it is:
JSON (JavaScript Object Notation) is the text format used to send data between servers and clients. Every
API you'll ever call returns JSON. Every request body you send will be JSON. You need to understand it
completely.
What to study:
• What JSON is — a text format derived from JS object syntax
• JSON vs JS object — key differences: keys must be double-quoted strings, no undefined, no functions,
no comments
• [Link](value) — convert JS value to JSON string
• [Link](string) — parse JSON string back to JS value
• [Link] with replacer and space arguments — filtering and formatting
• [Link] with reviver — transforming values while parsing
• toJSON() method on objects — customize how an object is stringified
• What gets lost in JSON serialization: functions, undefined, Symbol, Date (becomes string), circular
references
• Handling circular references — [Link] throws on circular refs
• Pretty printing: [Link](obj, null, 2)
UNIT 4
Selecting Elements
2 Finding elements on the page from JS
Events
5 Responding to user actions
What it is:
When a browser loads an HTML file, it parses the HTML and builds a tree of objects in memory. This tree is
the DOM — Document Object Model. Every HTML element becomes an object (a 'node') in the tree. JS can
then interact with these objects to read or change anything on the page.
What to study:
• The browser's rendering pipeline: HTML parsing → DOM tree → CSSOM → Render tree → Layout →
Paint
• What the DOM is — a tree of node objects representing the HTML document
• Node types: Element nodes (div, p, button), Text nodes, Comment nodes, Document node
• The document object — the entry point to the DOM from JS
• window vs document — window is the global object, document is the DOM root
• DOM vs HTML — the DOM is a live object tree, HTML is just text
• The DOM is live — JS changes to the DOM immediately update the page
• How to include JS in HTML: <script> tag placement (end of body, defer, async)
• DOMContentLoaded event — fires when HTML is parsed. load event — fires when everything including
images is loaded
What it is:
Before you can manipulate anything, you need to find it. JS gives you several methods to search the DOM.
querySelector is the most flexible and the one you'll use most.
What to study:
• [Link]('id') — fastest, returns single element
• [Link]('.class') — returns FIRST match, accepts any CSS selector
• [Link]('.class') — returns NodeList of ALL matches
• [Link]('class') — returns live HTMLCollection
• [Link]('div') — returns live HTMLCollection
• CSS selectors you can use in querySelector: #id, .class, tag, [attr], [Link], parent > child
• NodeList vs HTMLCollection — different types, NodeList from querySelectorAll is static, from
childNodes is live
• Traversing the DOM tree: parentElement, children, firstElementChild, lastElementChild
• nextElementSibling, previousElementSibling
• closest(selector) — walk UP the tree to find a matching ancestor
• matches(selector) — check if element matches a CSS selector
What it is:
You can build new HTML elements from scratch in JS, customize them, and insert them anywhere in the
DOM. You can also remove elements. This is how dynamic UIs are built — not by loading new pages, but by
modifying the existing DOM.
What to study:
• [Link]('tag') — create a new element in memory (not on page yet)
• [Link](child) — add child at the end of element
• [Link](...nodes) — add one or more nodes/strings at end
• [Link](...nodes) — add at start
• [Link](), [Link]() — insert before/after element (as sibling)
• [Link](position, html) — insert HTML at specific position
• [Link]() — remove element from DOM
• [Link](newElement) — replace element
• [Link](deep) — deep clone copies element and all children
• DocumentFragment — batch DOM operations in memory before inserting (performance)
• innerHTML = '' vs removeChild loop — clearing element's children
TOPIC 5 — Events
What it is:
Events are things that happen in the browser — user clicks, keys pressed, pages loading, timers firing. JS lets
you attach listener functions that run when these events occur. This is the foundation of interactivity.
What to study:
• addEventListener(event, handler) — the correct way to attach events
• removeEventListener() — detaching listeners (must use same function reference)
• The Event object — passed to every handler: [Link], [Link], [Link]
• Mouse events: click, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave
• Keyboard events: keydown, keyup — [Link], [Link], [Link], [Link]
• Form events: submit, change, input, focus, blur
• Window events: load, DOMContentLoaded, resize, scroll
• preventDefault() — stop default browser behavior (e.g., stop form from submitting)
• Inline HTML event handlers ( — understand them, but don't use them
• The old way: [Link] = fn — understand it, prefer addEventListener
What it is:
When an event fires on an element, it doesn't just stay there. It travels up through the DOM tree (bubbling)
from the target to the root. This behavior, once understood, enables a powerful pattern called event delegation
— attaching a single listener on a parent to handle events from many children.
What to study:
• Event propagation — 3 phases: capturing (down), target, bubbling (up)
• Bubbling — event travels from target element up to the document root
• Capturing — event travels from root down to target (rarely used)
• addEventListener third argument: true for capturing phase
• [Link] — the element that triggered the event
• [Link] — the element the listener is attached to
• stopPropagation() — stop the event from bubbling further up
• stopImmediatePropagation() — stop bubbling AND prevent other listeners on same element
• Event delegation — attach ONE listener on parent, check [Link] to handle children
• Why delegation is better for dynamic children — children added later are automatically handled
• Real example: click listener on a ul handles clicks on any li, even ones added later
What it is:
Forms are how users send data. By default, submitting a form reloads the page — JS lets you intercept this,
validate the data, and handle it yourself. This is how every modern web app works.
What to study:
• Accessing form element: [Link]('form') or [Link]
• Accessing form fields by name: [Link]
• Getting input value: [Link] — always a string
• Checkboxes: [Link] — returns boolean
• Radio buttons: loop through and check .checked
• Select dropdowns: [Link] — current selected value
• preventDefault() on submit event — prevent page reload
• Input validation: check required fields, email format, length
• Input events: 'input' fires on every keystroke, 'change' fires when field loses focus
• focus() and blur() — programmatically focus/unfocus elements
• FormData object — collect all form values at once
What it is:
localStorage persists data between page sessions. sessionStorage persists for the current tab only.
setTimeout and setInterval let you run code after a delay or repeatedly. These are browser-specific APIs —
they don't exist in [Link].
What to study:
• [Link](key, value) — store a value
• [Link](key) — retrieve a value
• [Link](key), [Link]()
• localStorage only stores strings — must [Link] objects before saving
• [Link]() returns null if key doesn't exist — always check
• sessionStorage — same API as localStorage, clears when tab is closed
• localStorage vs sessionStorage vs cookies — when to use each
• setTimeout(fn, ms) — run fn once after ms milliseconds. Returns a timer ID
• clearTimeout(timerId) — cancel a pending timeout
• setInterval(fn, ms) — run fn every ms milliseconds. Returns a timer ID
• clearInterval(timerId) — cancel a repeating interval
• Why setTimeout(fn, 0) doesn't mean instant — it queues the callback in the event loop
UNIT 5
OOP in JavaScript
JS is prototype-based, not class-based like Java. ES6 added class syntax but it's just
sugar over prototypes — the underlying mechanism is completely different from Java.
Understanding the prototype chain is essential for understanding the entire language.
⏱ Time Estimate: 40–55 hours total (30–40h study + 10–15h project)
ES6 Classes
3 The modern syntax — syntactic sugar over prototypes
Property Descriptors
7 The metadata behind every object property
What it is:
Every object in JS has a hidden property called [[Prototype]]. This property points to another object — the
prototype. When you try to access a property that doesn't exist on the object, JS automatically looks at the
prototype, then the prototype's prototype, and so on up the chain until it reaches null. This chain is how
inheritance works in JS.
What to study:
• [[Prototype]] — the hidden link every object has to another object
• [Link](obj) — read the prototype
• [Link](obj, proto) — set the prototype (avoid in production code)
• __proto__ — the accessor property that exposes [[Prototype]] (deprecated, but you'll see it)
• The prototype chain — looking up properties by walking the chain
• null at the end — [Link]'s prototype is null, that's where the chain ends
• [Link] — the base prototype. Every object inherits from it unless you use [Link](null)
• hasOwnProperty() — check if property is own (not inherited). [Link]() is the modern version
• The for...in loop and the prototype chain — why it finds inherited properties
• Why [Link] exists — arrays inherit it through the chain
What it is:
Before ES6 classes, you created object blueprints with constructor functions. When you call a function with
'new', JS does 4 things automatically. Knowing these 4 steps is what makes 'new' understandable — and
helps you understand what classes compile to.
What to study:
• Constructor function convention — capitalize the name: function Person(name) {}
• The new keyword — what happens step by step:
• Step 1: Creates a new empty object {}
• Step 2: Sets the object's [[Prototype]] to [Link]
• Step 3: Calls the constructor with 'this' set to the new object
• Step 4: Returns the new object (unless constructor explicitly returns an object)
• [Link] — the object that new instances inherit from
• Adding methods to [Link] — shared across all instances (not duplicated per instance)
• instanceof — checks if [Link] is in the object's prototype chain
• What happens if you call a constructor without new — 'this' is global/undefined, disaster
• [Link] — inside a constructor, tells you if it was called with new
What it is:
ES6 introduced class syntax that looks like Java. But remember: it's syntactic sugar. Under the hood, it's still
prototypes and constructor functions. Classes are cleaner and more readable but understanding that they
compile to prototype-based code is crucial.
What to study:
• class declaration syntax: class Person { constructor() {} }
• class expression: const Person = class { ... }
• The constructor() method — called on new instantiation
• Instance methods — defined in the class body, added to prototype
• Instance properties — defined in constructor with [Link] = value
• Class fields (ES2022) — define properties directly in class body without constructor
• Classes are NOT hoisted like function declarations
• Classes are always in strict mode
• typeof ClassName === 'function' — classes are functions under the hood
• [Link] — where instance methods live
🔩 Under the Hood — Go here after the surface clicks.
– What the JavaScript engine actually does with class syntax — it's a function with methods added
to its prototype
– Class fields vs prototype methods — class fields are OWN properties (set on the instance),
prototype methods are SHARED
– Verify: inspect [Link] in the console and see the methods there
What it is:
The extends keyword sets up prototype chain inheritance between classes. super() calls the parent class
constructor. This gives you Java-like inheritance syntax while keeping the JS prototype chain underneath.
What to study:
• extends — class Dog extends Animal — sets up the prototype chain
• super() — must be called in child constructor BEFORE using 'this'
• Why super() must come first — the parent constructor creates the object, the child adds to it
• Calling parent methods: [Link]()
• Overriding methods — define a method with the same name in the child class
• instanceof with inheritance: new Dog() instanceof Animal — true, checks the full chain
• The prototype chain with classes: dog → [Link] → [Link] → [Link] → null
• Checking the exact type: [Link] === Dog
• Abstract class pattern — classes you don't instantiate directly (no native abstract in JS)
• Mixins — a pattern for sharing behavior across unrelated classes without inheritance
What it is:
Private fields (using #) are a true privacy mechanism added in ES2022. Unlike closures-based privacy (the old
pattern), private fields are enforced by the engine — no workaround. This is the modern way to encapsulate
data in JS classes.
What to study:
• Private field syntax: #fieldName — declared at class body level
• Accessing private fields: this.#fieldName — only accessible inside the class
• Trying to access from outside throws: SyntaxError
• Private methods: #methodName() {}
• Private static fields and methods
• Why private fields are NOT the same as closure-based privacy
• Checking if object has a private field: #field in obj — works inside the class
• The old pattern: closure-based privacy using constructor function and closure
• WeakMap-based privacy — the pre-ES2022 pattern worth knowing
What it is:
Every property on an object has metadata attached to it — three boolean flags: writable (can the value be
changed?), enumerable (does it show up in for...in and [Link]()?), configurable (can the property be
deleted or its descriptor changed?). By default when you assign a property, all three are true. Built-in methods
often have them as false.
What to study:
• [Link](obj, 'prop') — read a property's descriptor
• [Link](obj) — get all descriptors
• [Link](obj, 'prop', descriptor) — define or modify with full control
• writable: false — value cannot be changed (silently fails outside strict mode, throws in strict)
• enumerable: false — property hidden from for...in and [Link]()
• configurable: false — property cannot be deleted, descriptor cannot be changed
• Data descriptor vs accessor descriptor — a property can have a value OR a getter/setter, not both
• [Link]() — makes all properties non-writable and non-configurable (shallow)
• [Link]() — makes all properties non-configurable but keeps them writable
• [Link](), [Link](), [Link]()
• [Link]() — no new properties can be added
What it is:
Getters and setters are special methods that act like properties. When you read [Link], it calls the
getter. When you assign [Link] = 'John Doe', it calls the setter. They let you compute values on the fly,
validate input, and run side effects on property access.
What to study:
• get keyword: get fullName() { return [Link] + ' ' + [Link] }
• set keyword: set age(value) { if (value < 0) throw new Error(); this._age = value }
• Using getters/setters in object literals
• Using getters/setters in classes
• Getter with no setter — read-only property
• Convention: _propertyName for the backing store
• [Link]() with get and set for accessor descriptors
• When to use getters: computed properties, lazy initialization, compatibility aliases
What it is:
[Link](proto) creates a new object with a specified prototype. This gives you direct control over the
prototype chain without using classes or constructor functions. It's the most explicit way to set up prototype-
based inheritance.
What to study:
• [Link](proto) — create object with proto as its prototype
• [Link](null) — create object with NO prototype (no inherited methods — pure hash map)
• Using [Link] for inheritance without classes
• [Link] with property descriptors as second argument
• Setting up prototype chains manually — understanding what extends does
• [Link]() and [Link]()
• Why [Link]() is slow and should be avoided
• Checking prototype chain: [Link](obj)
UNIT 6
Asynchronous JavaScript
This is the hardest unit. Async JS is the foundation of [Link], all web APIs, and the
MERN stack. The event loop, promises, and async/await are not optional knowledge —
they are the language. Take your time here.
⏱ Time Estimate: 55–75 hours total (40–55h study + 15–20h project)
Promises — Basics
5 The solution to callback hell
Promises — Combinators
6 Running multiple async operations
async / await
7 The clean syntax over promises
Error Handling in Async Code
8 try/catch with async, rejected promises, unhandled rejections
Advanced Patterns
10 Timeout, retry, cancellation, concurrency control
What it is:
JavaScript has ONE thread of execution. It can only do one thing at a time. It cannot truly run two pieces of
code simultaneously. But it can WAIT for things (network requests, timers) without blocking — this is non-
blocking I/O. Understanding this distinction is the key to understanding all of async JS.
What to study:
• What a thread is — a sequence of instruction execution
• Single-threaded — one thread means one thing executing at a time
• Why single-threaded — JS was designed for the browser, shared state with one UI thread is simpler
• Blocking vs non-blocking — blocking halts the thread. Non-blocking delegates and continues.
• Synchronous code — runs line by line, each line blocks the next
• Asynchronous code — you start an operation, provide a callback, and continue running other code
• Why a slow synchronous operation (like a while loop) freezes the browser entirely
• Concurrency vs parallelism — JS achieves concurrency (interleaving) not parallelism (simultaneous)
What it is:
This is the mechanism that makes JS asynchronous without being multi-threaded. The event loop
continuously checks: is the call stack empty? If yes, take the next task from the queue and push it onto the
stack. There are two queues: the macrotask queue (tasks) and the microtask queue (microtasks — which run
first).
What to study:
• The event loop — a loop that checks if the call stack is empty, then processes queued callbacks
• The macrotask queue — where setTimeout, setInterval, I/O callbacks, and UI events go
• The microtask queue — where Promise .then() callbacks and queueMicrotask() callbacks go
• CRITICAL: microtasks run after the current task but BEFORE the next macrotask
• Processing order: synchronous code → microtasks → macrotask → microtasks → macrotask...
• Why [Link]().then() runs before setTimeout(fn, 0)
• MutationObserver callbacks — also microtasks
• queueMicrotask() — manually queue a microtask
• Why starving the event loop is dangerous — infinite microtasks prevent the next macrotask from ever
running
• Visualize this: use [Link] and watch the event loop in action
🔩 Under the Hood — Go here after the surface clicks.
– The HTML spec defines the event loop — not the ECMAScript spec. The browser implements it.
– [Link] has its own event loop (implemented by libuv) with phases: timers, I/O callbacks, idle, poll,
check, close. More phases than browser.
– The microtask checkpoint — runs after EVERY task, not just when the call stack is clear
– Read: [Link]/multipage/[Link]#event-loops
– Read: [Link]/2015/tasks-microtasks-queues-and-schedules/ — the best article on this
What it is:
Before Promises, async code was handled with callbacks. You pass a function to be called when the async
operation completes. This worked but led to deeply nested code when operations depended on each other —
called 'callback hell' or the 'pyramid of doom'.
What to study:
• Callback pattern — passing a function to be called later
• Synchronous callbacks — forEach, map (called immediately, not async)
• Asynchronous callbacks — setTimeout, event listeners, HTTP requests
• Error-first callbacks — [Link] convention: first argument is error or null
• Callback hell — nested callbacks for sequential async operations
• Problems: hard to read, hard to error handle, hard to compose
• Why promises were invented — to solve callback hell
What it is:
A Promise is an object representing the eventual completion or failure of an async operation. It has three
states: pending, fulfilled (resolved with a value), or rejected (failed with a reason). Promises let you chain
async operations and handle errors in one place.
What to study:
• Creating a Promise: new Promise((resolve, reject) => { ... })
• resolve(value) — fulfill the promise with a value
• reject(reason) — reject the promise with an error
• Promise states: pending → fulfilled or pending → rejected (states are final once set)
• .then(onFulfilled) — callback when promise resolves
• .catch(onRejected) — callback when promise rejects
• .finally(fn) — always runs, resolved or rejected
• Promise chaining — .then() returns a new Promise, enabling chaining
• Returning values in .then() — the value becomes the resolved value of the next promise
• Returning a Promise in .then() — the chain waits for it
• Error propagation — a rejection skips .then() handlers until it hits a .catch()
• [Link](value) and [Link](reason) — create immediately settled promises
What it is:
Promise combinators let you manage multiple async operations at once. [Link]() is the one you'll use
most — it runs multiple promises in parallel and waits for all of them. The others handle different failure
scenarios.
What to study:
• [Link]([p1, p2, p3]) — wait for ALL to resolve. If ANY rejects, immediately rejects.
• [Link]([p1, p2]) — wait for ALL to settle (resolve OR reject). Never rejects.
• [Link]([p1, p2]) — resolves/rejects with the FIRST settled promise
• [Link]([p1, p2]) — resolves with the FIRST fulfilled promise. Rejects only if ALL reject.
• When to use each: all (need all results), allSettled (need all results even with failures), race (timeout
pattern), any (first success wins)
• Practical example: fetching data from multiple APIs in parallel with [Link]()
• Timeout pattern with [Link]([fetch(...), timeoutPromise(5000)])
🔩 Under the Hood — Go here after the surface clicks.
– [Link] internally tracks pending count — when all resolve, it resolves with an array of results
in the same order as input
– [Link] always resolves (never rejects) — each result is { status: 'fulfilled'/'rejected',
value/reason }
What it is:
async/await is syntactic sugar over Promises. An async function always returns a Promise. Inside it, you can
use await to pause execution until a Promise settles — this makes async code look and behave like
synchronous code. It's much more readable than .then() chains for most cases.
What to study:
• async function declaration: async function fetchData() {}
• async arrow: const fetchData = async () => {}
• async functions always return a Promise — even if you return a plain value
• await keyword — pauses the function until the Promise settles. Returns the resolved value.
• await only works inside an async function (or top-level in modules — ES2022)
• Error handling: try/catch around await — catches rejected promises
• Running parallel operations: const [a, b] = await [Link]([fetch1, fetch2])
• MISTAKE: await in a loop one by one — sequential, slow. Use [Link] instead.
• Async IIFE: (async () => { ... })() — run async code at top level
• Top-level await — available in ES modules without wrapping in async function
What it is:
fetch() is the browser's built-in way to make HTTP requests. It returns a Promise. Every MERN application you
build will use fetch (or a library built on it). You need to understand HTTP requests and responses, not just the
fetch() API itself.
What to study:
• What HTTP is — the protocol for sending data between client and server
• HTTP methods: GET (read), POST (create), PUT/PATCH (update), DELETE
• HTTP status codes: 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500
Server Error
• fetch(url) — basic GET request
• The Response object: [Link], [Link], [Link]
• [Link]() — parse response body as JSON. Returns a Promise.
• [Link](), [Link]() — other response formats
• POST request with fetch: { method: 'POST', headers: {...}, body: [Link](data) }
• Content-Type header: 'application/json' — tells server the format
• CORS — what it is and why fetch sometimes fails cross-origin
• fetch does NOT reject on HTTP errors (404, 500) — check [Link] manually
• Proper error handling: if (![Link]) throw new Error([Link])
What it is:
Real applications need more than basic fetch and await. Timeout patterns, retry logic, concurrency control,
and cancellation are all patterns you'll need. These are not built into the language — you implement them
using the primitives you've learned.
What to study:
• Timeout pattern: race a fetch against a Promise that rejects after N seconds
• Retry pattern: retry a failed async operation N times with delay
• Exponential backoff — increasing delay between retries
• Concurrency control — limit to N parallel requests at a time
• AbortController — cancel a fetch request in progress
• AbortController with signal: fetch(url, { signal: [Link] })
• Calling [Link]() cancels the fetch
• Sequential vs parallel — knowing when each is appropriate
• Async iterators and for await...of — iterating async data sources
• Throttled fetch — debouncing API calls on user input
UNIT 7
CommonJS vs ES Modules
2 [Link] uses both — you need to know the difference
Symbols
3 Unique keys — more powerful than they appear
Generators
5 Functions that can pause and resume
What it is:
Modules let you split code across files and control what's public and what's private. Each module has its own
scope — variables don't leak to global. export marks things as public. import brings them in. This is how every
modern JS application is structured.
What to study:
• Named exports: export const name = ... or export { name, fn }
• Default export: export default function() {} — one per module
• Named import: import { name, fn } from './[Link]'
• Default import: import myName from './[Link]' — any name works
• Import both: import defaultExport, { namedExport } from './[Link]'
• Import all: import * as utils from './[Link]'
• Rename on import: import { name as firstName } from './[Link]'
• Re-exporting: export { name } from './[Link]'
• Dynamic imports: import('./[Link]').then(module => ...) — load on demand
• await import('./[Link]') — with top-level await
• Module script type: <script type='module'> — required in browser
• Modules are singletons — importing the same module twice gives the same instance
• Modules are strict mode by default
What it is:
[Link] was built with CommonJS (require/[Link]) before ES modules existed. ES modules were
added later. Today [Link] supports both, but they behave differently. You'll encounter both in the MERN
stack — React uses ES modules, many [Link] packages still use CommonJS.
What to study:
• CommonJS: const mod = require('./module')
• CommonJS: [Link] = { ... } or [Link] = ...
• CommonJS is synchronous — require() is a blocking operation that runs the module immediately
• ES Modules: import/export — you've just learned these
• ES Modules are asynchronous — loaded and evaluated asynchronously
• File extension: .js can be either (determined by [Link] type field), .cjs is always CommonJS, .mjs
is always ES Module
• [Link]: 'type': 'module' — makes all .js files in the package ES modules
• Mixing them — you CAN import CJS from ESM but not vice versa in some cases
• Why MERN uses both — [Link] server code often CommonJS, React code always ES modules
• Interop: import cjsModule from './[Link]' — default import gets [Link]
TOPIC 3 — Symbols
What it is:
A Symbol is a unique, primitive value created by Symbol(). Every Symbol() call creates a new, unique symbol
— even if you pass the same description. Symbols are primarily used as object property keys that never
conflict with string keys, and for defining special behaviors via 'well-known symbols'.
What to study:
• Creating symbols: const id = Symbol('description')
• Every symbol is unique: Symbol('id') !== Symbol('id')
• Symbols as object keys: obj[id] = value — accessed only via bracket notation
• Symbol properties are hidden from for...in, [Link](), [Link]()
• [Link]() — get symbol keys
• Global symbol registry: [Link]('key') and [Link](sym)
• Well-known symbols — used to customize JS behavior:
• [Link] — makes an object iterable (for...of)
• [Link] — customize type conversion
• [Link] — customize instanceof
• [Link] — customize [Link] output
What it is:
The iteration protocol is a standard way any object can say 'I am iterable'. An iterable has a [[Link]]()
method that returns an iterator. An iterator has a next() method that returns { value, done }. This is how for...of
works on arrays, strings, Maps, Sets, and generators.
What to study:
• The iterable protocol — an object with [[Link]]() method
• The iterator protocol — an object with next() that returns { value, done }
• Built-in iterables: Array, String, Map, Set, arguments, NodeList
• What for...of does — calls [[Link]](), then repeatedly calls next()
• Destructuring arrays uses the iteration protocol
• Spread on iterables: [...iterable]
• Making a custom iterable object from scratch
• Making an infinite iterator — one that never returns done: true
• Iterator composition — iterators that wrap other iterators
TOPIC 5 — Generators
What it is:
A generator function can pause its execution using yield and resume later. It returns a generator object that
implements the iterator protocol. Generators are useful for lazy sequences, infinite data streams, and they are
the foundation of how async/await was originally implemented.
What to study:
• Generator function syntax: function* myGen() {}
• yield — pauses execution and returns a value to the caller
• next() returns { value, done }
• Resuming with next(value) — pass a value back into the generator
• Generator as iterable — use with for...of, spread
• Finite generators — yield a finite sequence, then done: true
• Infinite generators — while(true) { yield something } — lazy, only computes next when asked
• Generator delegation: yield* anotherGenerator()
• return inside generator — terminates early
• try/finally in generators — finally runs when generator is closed
• throw() on generator — inject an error into the generator
• Practical uses: infinite sequences, lazy evaluation, state machines, co-routines
• async generators: async function* — produces values asynchronously, consumed with for await...of
What it is:
JS evolves every year. TC39 (the standards committee) adds features through a proposal process. Here is
every feature from ES2016 to ES2024 that you need to know, organized by year.
What to study:
• ES2016: [Link]() — includes(value) returns boolean (better than indexOf()).
Exponentiation operator **
• ES2017: [Link](), [Link](). [Link]() / padEnd(). Trailing commas in
function params. Async functions (async/await).
• ES2018: Rest/spread for objects. [Link](). Async iteration (for await...of). RegExp named
groups (?<name>), lookbehind assertions.
• ES2019: [Link]() / flatMap(). [Link](). [Link]() / trimEnd(). Optional
catch binding: catch { } instead of catch(e) { }. [Link]() now guaranteed stable.
• ES2020: BigInt. Optional chaining (?.). Nullish coalescing (??). [Link](). globalThis.
Dynamic import(). [Link]().
• ES2021: [Link](). [Link](). Logical assignment operators: &&=, ||=, ??=. WeakRef and
FinalizationRegistry. Numeric separators: 1_000_000.
• ES2022: Top-level await in modules. Class fields and private (#). Static class blocks. [Link]().
[Link](). Error cause: new Error('msg', { cause: originalError }). RegExp /d flag (indices).
• ES2023: [Link]() / findLastIndex(). [Link](), toReversed(), toSpliced(), with() — non-
mutating versions. [Link] / using declaration.
• ES2024: [Link](). [Link]() / [Link](). [Link]().
RegExp v flag.
🔩 Under the Hood — Go here after the surface clicks.
– TC39 proposal process — stage 0 (idea) → stage 1 (proposal) → stage 2 (draft) → stage 3
(candidate) → stage 4 (finished). Features at stage 4 are added to the spec.
– Browser compatibility — not all features are available everywhere. Check: [Link] and MDN
compatibility tables.
– Transpilers like Babel — transform new JS syntax to older syntax for compatibility
📖 Read On: [Link]/proposals (finished proposals) · MDN: What's new in JavaScript · [Link]
What it is:
Proxy lets you intercept and redefine fundamental operations on objects — property access, assignment,
deletion, function calls. Reflect provides methods that mirror these same operations. Together they enable
meta-programming: writing code that controls how other code behaves.
What to study:
• Proxy: const proxy = new Proxy(target, handler)
• Handler traps: get, set, has, deleteProperty, apply, construct
• get trap: intercept property reads — return custom values or log access
• set trap: intercept property writes — validate before storing
• has trap: intercept the 'in' operator
• deleteProperty trap: intercept delete operator
• apply trap: intercept function calls (target must be a function)
• Reflect API: [Link](), [Link](), [Link](), etc.
• Using Reflect inside traps — always call Reflect to perform the default operation after your custom logic
• Revocable proxies: [Link]() — create a proxy you can disable
• Practical uses: validation, logging, default values for missing properties, reactive data (how [Link] 3
works)
• Proxy limitations — cannot trap private fields (#), some internal operations
UNIT 8
Functional Programming
4 A different way to think about code
Design Patterns
6 Proven solutions to recurring problems
Regular Expressions
8 Pattern matching — powerful and necessary
What it is:
Hoisting is the result of the creation phase — before any code runs, JS sets up variable and function
declarations. The behavior differs between var, let, const, function declarations, function expressions, and
class declarations. Getting this completely straight prevents a category of subtle bugs.
What to study:
• Function declarations are fully hoisted — available before their definition in code
• var is hoisted and initialized to undefined — can be used before assignment (value is undefined)
• let and const are hoisted but NOT initialized — in the Temporal Dead Zone until their declaration
• Temporal Dead Zone (TDZ) — the period between entering scope and the declaration line
• Accessing a let/const in the TDZ throws a ReferenceError
• Function expressions (var fn = function(){}) — hoisted as var, undefined until assignment line
• Class declarations — hoisted but in TDZ (like let/const), cannot be used before declaration
• Hoisting inside blocks — let/const are block-scoped and hoisted to the top of their block
• Hoisting order: function declarations are processed before var declarations in the same scope
What it is:
JS manages memory automatically — you don't call malloc/free like in C++. The garbage collector (GC)
automatically frees memory that is no longer reachable. Understanding how it works helps you avoid memory
leaks — situations where you accidentally hold references to things you no longer need.
What to study:
• Memory lifecycle: allocate → use → release
• Automatic allocation — JS allocates when you declare variables, create objects, call functions
• The Mark and Sweep algorithm — the GC marks all reachable objects from roots, sweeps away the
rest
• Roots — global variables, the call stack, and any references from those
• Memory leaks — holding unnecessary references that prevent GC from collecting objects
• Common memory leak sources:
• Accidental global variables — assigning without var/let/const in non-strict mode
• Forgotten timers — setInterval referencing objects, never cleared
• DOM references — storing reference to removed DOM node
• Closures holding large objects in scope unnecessarily
• Event listeners not removed
• WeakRef — a reference that doesn't prevent GC
• FinalizationRegistry — callback when a weakly-referenced object is collected
• Detecting leaks — using Chrome DevTools Memory panel, heap snapshots
What it is:
Functional programming (FP) is a programming paradigm that treats computation as the evaluation of
functions and avoids mutable state and side effects. JS supports FP alongside OOP. Many modern JS
patterns (especially in React) are heavily functional.
What to study:
• Pure functions — same input, always same output. No side effects.
• Side effects — anything that affects the outside world: network calls, file writes, DOM changes,
modifying outer variables
• Immutability — never mutate data, always create new data
• Immutable updates for objects: { ...original, changedProp: newValue }
• Immutable updates for arrays: [...[Link](0,i), newItem, ...[Link](i+1)]
• Function composition — combining simple functions to build complex ones
• compose(f, g)(x) = f(g(x)) — right to left
• pipe(f, g)(x) = g(f(x)) — left to right
• Avoiding shared state — each function works with its arguments, not external variables
• Functors — things that can be mapped over (arrays implement map = they are functors)
• Point-free style — defining functions without mentioning their arguments
• Referential transparency — a function call can be replaced with its return value without changing
behavior
What it is:
Currying transforms a function that takes multiple arguments into a sequence of functions each taking one
argument: f(a, b) becomes f(a)(b). Partial application pre-fills some arguments of a function, returning a new
function for the rest. Both are tools for creating specialized functions from general ones.
What to study:
• Currying: const add = a => b => a + b — add(3)(4) = 7
• Why currying is useful — creating specialized versions of general functions
• curry(fn) — writing a utility function that curries any function
• Partial application with bind: const add5 = [Link](null, 5)
• Partial application without bind: const partial = (fn, ...args) => (...rest) => fn(...args, ...rest)
• Difference: currying always takes one argument at a time, partial applies some arguments at once
• Real use: event handlers with data: [Link](item => [Link]('click',
handleClick(item)))
• Function composition with curried functions
What it is:
Design patterns are proven, reusable solutions to commonly occurring problems in software design. They are
not code you copy — they are concepts you apply. Knowing these gives you a vocabulary for discussing code
architecture and a toolkit for solving structural problems.
What to study:
• Module Pattern — encapsulate private state using closures. The basis of all JS modules before ES6.
• Singleton Pattern — ensure only one instance exists. Often implemented with closures or modules.
• Observer / Pub-Sub Pattern — objects subscribe to events, publisher notifies all subscribers.
Foundation of event systems.
• Factory Pattern — a function that creates and returns objects. Alternative to new and classes.
• Builder Pattern — construct complex objects step by step through chained methods.
• Strategy Pattern — define a family of algorithms, encapsulate each, make them interchangeable.
• Decorator Pattern — add behavior to an object without changing its class (not to be confused with class
decorators).
• Command Pattern — encapsulate requests as objects. Enables undo/redo.
• Iterator Pattern — traverse a collection without exposing its internal structure (you know this — it's the
iteration protocol).
• Mediator Pattern — objects communicate through a central mediator instead of directly.
• Proxy Pattern — you know this — it's the Proxy API.
• Mixin Pattern — add methods from multiple sources to a class without inheritance.
What it is:
Real applications have performance problems. Debounce delays a function call until a pause in activity.
Throttle limits how often a function can be called. Lazy evaluation avoids computing values until they're
needed. These are tools you'll use in every production application.
What to study:
• Debounce — delay execution until N ms after the LAST call. Use case: search-as-you-type, resize
handler
• Implementing debounce from scratch using setTimeout and clearTimeout
• Throttle — execute at most once per N ms. Use case: scroll handler, game loop, rate-limited API
• Implementing throttle from scratch
• Debounce vs Throttle — which to use when
• Memoization — cache results of expensive function calls by their arguments
• Memoize with Map: key is serialized args, value is result
• Memoize with WeakMap for object arguments
• Lazy evaluation — don't compute until needed. Generators are lazy.
• requestAnimationFrame — run code before the next repaint. Better than setTimeout for animations.
• Web Workers — offload heavy computation to a background thread
• Profiling with DevTools — Performance panel, CPU profiling, memory profiling
• Common performance anti-patterns: DOM manipulation in loops, synchronous XHR, blocking the main
thread
What it is:
Regular expressions (regex) are patterns for matching text. They're powerful for validation, parsing, and find-
replace operations. JS has built-in regex support via the RegExp object and regex literals. You can't avoid
them in real development.
What to study:
• Creating regex: /pattern/flags or new RegExp('pattern', 'flags')
• Flags: g (global, all matches), i (case-insensitive), m (multiline), s (dotAll), u (unicode), d (indices)
• Character classes: \d (digit), \w (word char), \s (whitespace), \D \W \S (negated), . (any char)
• Custom character class: [abc], [a-z], [^abc] (negated)
• Quantifiers: * (0+), + (1+), ? (0 or 1), {n}, {n,}, {n,m}
• Greedy vs lazy quantifiers: .* vs .*?
• Anchors: ^ (start), $ (end), \b (word boundary)
• Groups: (pattern) — capturing group. (?:pattern) — non-capturing
• Named groups: (?<name>pattern) — access via [Link]
• Alternation: a|b — match a or b
• Lookahead: (?=pattern) — assert without consuming. Negative: (?!pattern)
• Lookbehind: (?<=pattern). Negative: (?<!pattern)
• Backreferences: \1 — match what group 1 matched
• Methods: [Link](), [Link](), [Link](), [Link](), [Link](), [Link](), [Link]()
• Using [Link] to test and debug patterns
What it is:
In small scripts, try/catch in one place is fine. In a real application, you need a systematic approach: custom
error classes for different failure types, consistent error handling across async code, meaningful error
messages, and global fallback handlers.
What to study:
• Custom error classes: class ValidationError extends Error { constructor(message, field) {} }
• instanceof for error type checking: catch(e) { if (e instanceof ValidationError) {} }
• Error wrapping — catch a low-level error and re-throw as a higher-level one
• Error cause (ES2022): throw new Error('Failed', { cause: originalError })
• Result type pattern — return { data, error } instead of throwing (functional error handling)
• Global error boundaries — try/catch at the top level of your application
• [Link] and [Link]('error') — browser global error handler
• [Link]('unhandledrejection') — catch unhandled Promise rejections
• Logging errors — what to log, how to log, structured error objects
• Error codes vs error messages — when to use each
UNIT 9
Hello [Link]
8 The framework that runs most [Link] servers
What it is:
[Link] is a JavaScript runtime built on V8. It gives JS capabilities it doesn't have in the browser: file system
access, network sockets, OS interaction. But it removes browser-specific things: no window, no document, no
DOM. Different globals exist instead.
What to study:
• Installing [Link] — use nvm (node version manager) to manage multiple versions
• Running a file: node [Link]
• The REPL — interactive Node shell: just type node
• Global objects in Node: global (equivalent to window), process, __dirname, __filename
• process object: [Link] (command line args), [Link] (environment variables), [Link](),
[Link]()
• [Link] — parsing command line arguments
• console in Node — same as browser: [Link], [Link], [Link], [Link]
• No window, no document, no XMLHttpRequest, no fetch (added in Node 18+)
• Buffer — Node's way of handling binary data
• global vs globalThis — globalThis works in both Node and browser
What it is:
The [Link] event loop is based on libuv, not the browser's HTML spec. It has the same basic concept (call
stack → queue → event loop) but with more phases. Understanding these phases explains why some
callbacks run before others in Node.
What to study:
• Node's event loop phases (in order): timers → pending callbacks → idle/prepare → poll → check →
close callbacks
• timers phase — executes setTimeout and setInterval callbacks whose delay has passed
• poll phase — retrieves new I/O events, executes their callbacks. Node waits here if nothing else to do.
• check phase — executes setImmediate() callbacks
• setImmediate vs setTimeout(fn, 0) — setImmediate runs in check phase (after I/O). setTimeout in
timers phase.
• [Link]() — NOT part of the event loop. Runs after current operation, before I/O events.
Higher priority than Promises.
• Priority order: [Link] > Promise microtasks > setImmediate > setTimeout(fn,0)
• Why [Link] can starve the event loop — infinite nextTick recursion prevents I/O from ever
being processed
What it is:
[Link] originally used CommonJS (require/[Link]). ES Modules (import/export) were added later.
Both coexist in modern Node. You need to know both because you'll encounter both in the wild, and mixing
them has rules.
What to study:
• CommonJS: const x = require('./module'); [Link] = ...; exports.x = ...
• CommonJS modules are cached — require() the same file twice returns the same cached exports
• CommonJS is synchronous — require() blocks until the module is evaluated
• ES Modules in Node: add 'type': 'module' in [Link] OR use .mjs extension
• ESM uses import/export — async, statically analyzed
• Importing CJS from ESM: works, default import gets [Link]
• Importing ESM from CJS: does NOT work directly (CJS is sync, ESM is async)
• Circular dependencies — how both systems handle them (and the bugs they cause)
• [Link]() — find the file path of a module without loading it
• __dirname and __filename in ESM — not available. Use [Link] instead.
• [Link] — the URL of the current module file
What it is:
npm is the package manager for [Link] and the largest software registry in the world. Understanding npm
properly — not just 'npm install' — is a professional skill. You need to understand the [Link] fields,
semantic versioning, the lock file, and how the node_modules directory works.
What to study:
• npm init and npm init -y — create a [Link]
• [Link] fields: name, version, description, main, scripts, dependencies, devDependencies,
engines
• npm install — install all dependencies. npm install packageName — add a dependency
• npm install --save-dev — add a devDependency (only needed for development, not in production)
• Semantic versioning: [Link] — 2.3.1
• Version ranges in [Link]: ^2.3.1 (minor+patch updates OK), ~2.3.1 (patch updates only), 2.3.1
(exact)
• [Link] — locks exact versions of all dependencies (and their dependencies)
• Why you commit [Link] — ensures everyone installs the same versions
• node_modules — where installed packages live. Never commit this folder.
• .npmrc — npm configuration file
• npm scripts: 'scripts': { 'start': 'node [Link]', 'test': 'jest' } — run with npm run start
• npx — run a package without installing it globally
• npm audit — check for security vulnerabilities in dependencies
• npm update — update packages within version ranges
• Global installs: npm install -g packageName — available system-wide
What it is:
[Link] ships with built-in modules for common server tasks. fs handles the file system. path handles file path
manipulation (crucial for cross-platform code). os gives system information. These are your tools before
installing any packages.
What to study:
• fs — file system:
• [Link](path, 'utf8', callback) — async read
• [Link](path, 'utf8') — sync read (blocks thread)
• [Link](path, data, callback) — async write
• [Link](), [Link](), [Link](), [Link]()
• [Link] — promisified versions: await [Link](...)
• fs/promises module — cleaner import for promise versions
• [Link]() — watch files for changes
• path — path manipulation:
• [Link]('dir', 'subdir', '[Link]') — cross-platform path joining
• [Link]() — absolute path resolution
• [Link](), [Link](), [Link]()
• [Link]() — break a path into components
• __dirname — current directory (CJS). [Link] + fileURLToPath for ESM.
• os module — system info:
• [Link](), [Link](), [Link](), [Link](), [Link](), [Link]()
What it is:
A Buffer is a fixed-size chunk of raw binary memory — Node's way of working with binary data. A Stream is a
sequence of data available over time. Instead of waiting for all data before processing, streams let you
process data as it arrives — essential for large files or network data.
What to study:
• Buffer — raw binary data: [Link]('hello'), [Link](16)
• Buffer encoding: utf8, hex, base64
• Why Buffers exist — Node handles binary data (files, network) before encoding to string
• Streams — four types: Readable, Writable, Duplex, Transform
• Reading a stream: [Link]('data', chunk => ...) and [Link]('end', () => ...)
• pipe() — connect a readable stream to a writable: [Link](writeStream)
• [Link]() and [Link]() — file streams
• Why streams matter — reading a 4GB file: readFileSync crashes (runs out of memory), a stream
processes it chunk by chunk
• Backpressure — when the consumer is slower than the producer, streams handle the buffering
• Transform streams — read, transform, write: like a zlib gzip compressor
What it is:
Before using Express, build a server manually with Node's built-in http module. This shows you exactly what
Express abstracts away — routing, parsing request bodies, setting response headers. Doing this once means
you'll never be confused by what Express is doing.
What to study:
• [Link]((req, res) => { ... }) — create a server
• req object: [Link], [Link], [Link]
• res object: [Link](statusCode, headers), [Link](data), [Link](data)
• Content-Type header — telling the client what format the response is in
• Manual routing: if ([Link] === '/users' && [Link] === 'GET') { ... }
• Parsing the request body — data comes as a stream: collect chunks, join on 'end'
• Parsing JSON body: [Link](body)
• URL parsing: new URL([Link], '[Link]
• Query string parsing: [Link]('name')
• [Link](port, hostname, callback) — start listening
• Returning JSON: [Link](200, { 'Content-Type': 'application/json' });
[Link]([Link](data))
• 404 for unmatched routes
What it is:
Express is the most popular [Link] web framework. It wraps Node's http module and adds routing,
middleware, request/response helpers, and a clean API. After building an http server from scratch, Express
will feel like a breath of fresh air.
What to study:
• Installing Express: npm install express
• Creating an Express app: const app = express()
• Defining routes: [Link]('/users', (req, res) => { ... })
• HTTP method routing: [Link](), [Link](), [Link](), [Link](), [Link]()
• Route parameters: [Link]('/users/:id', (req, res) => { [Link] })
• Query parameters: [Link]
• Request body: [Link] — requires middleware to parse
• [Link]() middleware — parse JSON request bodies
• [Link]() — parse form data
• [Link](data) — send JSON response
• [Link](404).json({ error: 'Not Found' }) — status + JSON
• [Link]() — mount middleware
• Middleware concept — functions that run between request and response
• [Link](3000, () => [Link]('Server running'))
• Express Router — splitting routes across multiple files
What it is:
Configuration that changes between environments (development, production) should never be hardcoded.
API keys, database URLs, port numbers — these go in environment variables. The dotenv package loads
them from a .env file in development.
What to study:
• [Link] — object with all environment variables
• Setting env vars: export PORT=3000 (Unix) or set PORT=3000 (Windows)
• The .env file — key=value pairs, one per line
• dotenv package: require('dotenv').config() — loads .env into [Link]
• .gitignore — always add .env to this. Never commit secrets.
• .[Link] — commit this with dummy values so others know what variables are needed
• Accessing: const PORT = [Link] || 3000 — fallback to default
• NODE_ENV variable — 'development', 'production', 'test' — controls app behavior
• config management for larger apps — organizing many environment variables
What it is:
Real code has bugs. Knowing how to debug efficiently is as important as knowing how to write code. [Link]
has a built-in debugger, VS Code has excellent [Link] debugging support, and there are logging tools you
should know.
What to study:
• [Link] is not a debugging strategy — but [Link], [Link], [Link] are useful
• [Link] built-in debugger: node inspect [Link]
• VS Code debugger — create [Link] for [Link]. Set breakpoints, step through code.
• Attaching VS Code to a running process: node --inspect [Link] then attach in VS Code
• Chrome DevTools for Node — open chrome://inspect when running with --inspect
• Debugger statement: debugger; — pauses execution when DevTools is open
• nodemon — automatically restart Node when files change: npx nodemon [Link]
• Environment-specific logging: only log verbose info in development
• Error stack traces — reading them, understanding file:line references
• Debugging async code — async stack traces, checking unhandled rejection logs