MindForge
MindForge
DashboardHelp & Support
Dashboard
MindForge

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
The DOM: Nodes, Events, and Traversal
Course contents

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Browsing without an account — progress is saved in this browser only.Sign in to save it

The DOM: Nodes, Events, and Traversal

Reading30 min

Before React, before any framework, there is the DOM: the browser's live, in-memory object tree built from your parsed HTML. It is not the HTML itself, and document is its root. Everything a framework does, eventually, is a more convenient way of mutating this tree. This lesson covers it directly: node types, traversal, events, and the one browser API, Intersection Observer, that replaced a genuinely bad older pattern for watching scroll position.

Node types, and the whitespace gotcha

Every node in the DOM has a numeric nodeType: 1 for an element, 3 for text (and yes, the whitespace between your tags counts as a text node too), 8 for a comment, 9 for the document itself, 11 for a document fragment. That whitespace detail matters the moment you start writing raw DOM traversal code: firstChild on an element with any leading whitespace in the source often hands you a text node, not the element you expected.

Remember: whitespace between tags is a real text node. firstChild can hand you that instead of the element you meant.

Sign in to try this knowledge check.

Two traversal vocabularies

There is an "all nodes" set of properties, parentNode, childNodes, firstChild, nextSibling, which includes text and comment nodes, and an "elements only" set, parentElement, children, firstElementChild, nextElementSibling, which skips them. closest(selector) walks up from an element until it finds a match, or returns null if it never does, the inverse of querySelector, which only ever looks downward.

Method Returns
getElementById a single element
getElementsByClassName / TagName a live HTMLCollection
querySelector / querySelectorAll a single element / a static NodeList

The word "live" there is not decoration. A live collection auto-updates as the DOM changes, which means mutating it while looping over it skips elements, since the collection is shrinking or growing under you mid-iteration. A querySelectorAll result is a frozen snapshot instead. If you need to safely mutate while iterating a live collection, convert it first with Array.from().

Remember: getElementsByClassName returns a live, auto-updating list. querySelectorAll returns a frozen snapshot. Mutating a live one mid-loop skips elements.

Sign in to try this knowledge check.

Creating and mutating

createElement, appendChild/append (which, unlike appendChild, accepts multiple nodes or plain strings at once), prepend, insertBefore, replaceChild, and .remove() cover most of what you will write by hand. cloneNode(deep) copies markup but never copies attached event listeners, a common source of "why did my clicks stop working" bugs. createDocumentFragment() exists specifically so you can build a batch of nodes off-screen and insert them all at once, one reflow instead of one reflow per insertion, which matters a lot once you are inserting dozens of rows in a loop.

Remember: cloneNode copies markup, never event listeners attached with addEventListener.

Sign in to try this knowledge check.

Attribute versus property: two different snapshots in time

An attribute is the string that came from your HTML source, and it is frozen at load time; getAttribute/setAttribute read and write it directly. A property is the live current state, el.value, el.checked, and it changes as the user interacts with the page. These diverge the moment a user does anything:

js
// <input value="hi">
el.value;               // whatever the user has typed now
el.getAttribute('value'); // still "hi", forever, regardless of what el.value says

Boolean attributes, checked, disabled, required, readonly, selected, work on presence, not on a string value: their mere existence in the markup means true, and their absence means false. When present, getAttribute returns "", not "true"; when absent, it returns null, never "false". To disable a button in JavaScript, set the property directly: button.disabled = true.

Remember: an attribute is frozen at load time. A property is live. An <input>'s typed value only ever shows up in the property.

Sign in to try this knowledge check.

innerHTML, textContent, and innerText aren't interchangeable

innerHTML parses whatever you assign it as HTML, which is a genuine XSS risk the moment the string could contain user input, and it re-renders the entire subtree. textContent is plain text, safe by construction, includes hidden text, and needs no reflow to read. innerText respects CSS visibility, it will not include text hidden with display: none, but forces a reflow to compute that, making it the slowest of the three. One more trap worth knowing: innerHTML += destroys and rebuilds every child node from scratch, which silently kills any event listeners already attached to them, even ones on children you did not intend to touch.

Remember: never assign user input to innerHTML. Use textContent, which treats it as plain text instead of parsing it as HTML.

Sign in to try this knowledge check.

Events: capturing, target, bubbling

The event flow has three phases: capturing (top of the tree down to the target), then the target itself, then bubbling (target back up to the top). Regular listeners run during the bubbling phase by default; pass { capture: true } to run during capturing instead. e.target is the actual element that was clicked; e.currentTarget is the element the listener is attached to, and they are often different once you are delegating.

stopPropagation() halts further bubbling or capturing. stopImmediatePropagation() does that and stops any other listeners on the same element from firing at all. preventDefault() cancels the browser's default action only, form submission, link navigation, and has nothing to do with propagation; the two are commonly confused but entirely unrelated. { once: true } auto-removes a listener after it fires once. removeEventListener only works when passed the exact same function reference given to addEventListener; an anonymous inline arrow function can never be removed later. { passive: true } is a promise to the browser that the listener won't call preventDefault(), which lets it start scrolling immediately instead of waiting to find out; calling preventDefault() anyway inside a passive listener is silently ignored.

Remember: stopPropagation() stops the event from spreading. preventDefault() stops the browser's default action. They are unrelated, despite sounding similar.

Sign in to try this knowledge check.

Event delegation

Put one listener on a parent instead of one on every child, and use e.target.closest(selector) inside it to find whichever descendant actually triggered the event:

js
document.querySelectorAll('.box').forEach(box => {
  box.addEventListener('click', (e) => {
    console.log(e.currentTarget.dataset.id);
  });
});

That specific example attaches a listener per box up front. Delegation's real advantage shows up for elements that don't exist yet at setup time: attach one listener to a stable ancestor, use closest() inside it, and it works for children added to the DOM later, since bubbling doesn't care when an element was created, only that it exists in the tree when the click happens. The one trap: if a child's own listener calls stopPropagation(), the event never reaches a delegated parent listener at all, and the parent's logic silently never runs.

You aren't limited to built-in event types. new CustomEvent('name', { detail, bubbles: true }) plus dispatchEvent() lets any part of the app announce something happened, with its own payload in detail, and, with bubbles: true, lets it be caught via delegation exactly like a native click.

Remember: one listener on a stable parent, plus closest() inside it, still catches clicks on children added to the page later.

Sign in to try this knowledge check.

Shadow DOM: a subtree the rest of the page can't see into

A Shadow DOM is an encapsulated subtree attached to an element, with its own scoped styles and markup, the mechanism behind Web Components (and behind native elements like <video>'s built-in controls, which are themselves shadow trees). Styles defined inside a shadow root don't leak out to the rest of the page, and page-level styles don't leak in, which is what makes a Web Component genuinely drop-in safe to reuse: no class-name collision is possible, because the shadow boundary isn't just a naming convention, it is an actual DOM isolation barrier.

Remember: a Shadow DOM boundary is a real isolation barrier, not a naming convention like BEM. Styles cannot cross it in either direction.

Sign in to try this knowledge check.

DOMContentLoaded versus window.onload

DOMContentLoaded fires once the HTML has been parsed and the DOM tree is ready, before images, stylesheets, and other subresources have necessarily finished loading. window.onload (or window.addEventListener('load', ...)) waits for everything on the page, images and CSS included, to finish loading too. Reach for DOMContentLoaded when a script only needs to query or manipulate the DOM structure itself; reach for load when it genuinely depends on a resource, measuring a loaded image's natural dimensions, for instance.

Remember: DOMContentLoaded means the HTML is parsed. load means every image and stylesheet is also done.

Watching visibility without a scroll listener

The old way to detect "has this element entered the viewport" combined a scroll event listener with getBoundingClientRect(). Scroll events fire constantly, and getBoundingClientRect() forces a layout reflow every time it is called, a direct instance of the layout-thrashing pattern from the rendering-performance lessons, just triggered by a library instead of a hand-written loop.

Intersection Observer replaces that entirely. It runs asynchronously, off the main thread's critical path, and only fires when an element's visibility actually changes.

js
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) console.log('Element visible:', entry.target);
  });
}, {
  root: null,        // the viewport, by default
  rootMargin: '0px',  // grows or shrinks the root's box for triggering purposes
  threshold: 0.5,      // fraction of the target that must be visible to fire
});

observer.observe(document.querySelector('.box'));

rootMargin: '100px' triggers the callback before an element is actually visible, useful for preloading a little ahead of the scroll position. threshold can be a single number or an array like [0, 0.5, 1] to fire at several visibility steps, handy for scroll-depth analytics. One observer instance can watch multiple elements at once; entries in the callback tells you which one fired, via entry.target. Beyond isIntersecting and target, each entry also carries intersectionRatio (exactly what percentage of the target is currently visible) and boundingClientRect/rootBounds (the raw geometry of the target and the root, for anything needing the actual pixel rect rather than just a boolean or a ratio).

Beyond infinite scroll and lazy-loaded images, the same primitive covers scroll-triggered animations (add a CSS class the instant an element enters view), ad viewability tracking (the industry-standard definition of a "viewable" impression is 50% visible for at least one continuous second, which threshold plus a bit of timing logic measures directly), and active nav highlighting (observe every section on the page and highlight whichever one's entry currently reports the highest isIntersecting/intersectionRatio).

jsx
function LazyImage({ src, alt }) {
  const imgRef = useRef(null);
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      { threshold: 0.1 },
    );
    if (imgRef.current) observer.observe(imgRef.current);
    return () => observer.disconnect();
  }, []);

  return <img ref={imgRef} src={isVisible ? src : undefined} alt={alt} />;
}

Trace it: the <img> renders with no src at all on mount, since isVisible starts false. The effect attaches an observer to the real DOM node via imgRef.current, and nothing loads until the image scrolls within 10% visibility, at which point setIsVisible(true) triggers a re-render that finally sets src, and observer.disconnect() stops watching, since a one-time lazy load has no reason to keep firing. If the component unmounts before that ever happens, the cleanup function disconnects the observer anyway, so nothing leaks.

Remember: Intersection Observer fires only when visibility actually changes, off the main thread's critical path. A scroll listener plus getBoundingClientRect() fires constantly and forces layout every time.

Sign in to try this knowledge check.

The pattern this API makes possible: infinite scroll

An invisible "sentinel" element sits at the very end of a list. An observer watches it, and when it becomes visible, the user has scrolled to the bottom, so the code fetches the next page and appends it.

jsx
function InfiniteList() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const sentinelRef = useRef(null);

  const fetchMore = useCallback(async () => {
    if (loading || !hasMore) return; // guard against double-fetch or fetching past the end
    setLoading(true);
    const res = await fetch(`/api/items?page=${page}`);
    const newItems = await res.json();
    if (newItems.length === 0) setHasMore(false);
    else {
      setItems(prev => [...prev, ...newItems]);
      setPage(prev => prev + 1);
    }
    setLoading(false);
  }, [page, loading, hasMore]);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => { if (entries[0].isIntersecting) fetchMore(); },
      { threshold: 1.0 },
    );
    const current = sentinelRef.current;
    if (current) observer.observe(current);
    return () => { if (current) observer.unobserve(current); };
  }, [fetchMore]);

  return (
    <div>
      {items.map(item => <div key={item.id}>{item.name}</div>)}
      <div ref={sentinelRef}>{loading && "Loading more..."}</div>
    </div>
  );
}

The loading/hasMore guard at the top of fetchMore matters as much as the observer itself: without it, a sentinel that's still visible while a request is in flight would fire the fetch again immediately on the next intersection check, and a sentinel for a list that's already exhausted would keep trying forever. For a genuinely large list built on top of infinite scroll, pair this with the Virtualization lesson, since fetching more pages is a separate problem from rendering all of them at once.

Remember: a sentinel-based infinite scroll needs a loading/hasMore guard, or a visible sentinel fires the fetch again on every intersection check.

Performance and cleanup, in one place

Cost ranking, most to least expensive: Reflow (a full layout recalculation), Repaint (pixels only, no layout change), Composite (GPU only, transform/opacity), the same hierarchy from the rendering-pipeline lesson, now grounded in DOM APIs specifically. Debounce fires once after activity pauses, good for a search input; throttle fires at fixed intervals during continuous activity, good for scroll tracking, both covered in depth in the Custom Hooks and Composition lesson. observer.unobserve(target) stops watching one element; observer.disconnect() stops watching everything, and forgetting either inside a useEffect cleanup function is how an Intersection Observer quietly leaks.

Remember: forgetting observer.disconnect() in a useEffect cleanup function is a quiet, common memory leak.

PreviousThe Event Loop and Browser Rendering PipelineNextPractice Test: HTML, CSS and JavaScript

Course progress

0%
0 of 177 modules complete

On this page

  • Node types, and the whitespace gotcha
  • Two traversal vocabularies
  • Creating and mutating
  • Attribute versus property: two different snapshots in time
  • innerHTML, textContent, and innerText aren't interchangeable
  • Events: capturing, target, bubbling
  • Event delegation
  • Shadow DOM: a subtree the rest of the page can't see into
  • DOMContentLoaded versus window.onload
  • Watching visibility without a scroll listener
  • The pattern this API makes possible: infinite scroll
  • Performance and cleanup, in one place