Infinite Scroll Feed with Caching

Objective

Implement a timeline-style feed that loads additional items as the user scrolls. Progress from a basic “load more on visibility” to a robust, cached, and optimistic system that behaves well under latency and failures.

Context

This exercise probes: IntersectionObserver or scroll-driven triggers, pagination state, duplicate prevention, caching, optimistic mutations, retry/backoff, and UX smoothness.

Setup

Dataset / API

Posts with fields: id, author, content, createdAt, liked (boolean), likeCount (number), media?: { url, width, height }.

Mock APIs

GET /api/posts?page=<n>&pageSize=<k>

  • → 200: { items: Post[], hasMore: boolean }

POST /api/posts/:id/like

  • body: { like: boolean }
  • → 200: { liked: boolean, likeCount: number }
  • → 409/500 sometimes; latency 150–900ms

(Inject randomized delay, occasional 429/500; sometimes duplicate items across pages to test de-dupe.)

Assumptions

  • React + TypeScript
  • IntersectionObserver preferred over scroll math
  • No external data libs required (you may discuss React Query-style patterns in Stage 4)

Stage 1 — Load More on Scroll

Goal

Render the first page and automatically load the next when the bottom sentinel becomes visible.

What to implement

  • Initial fetch of page 1 on mount
  • A sentinel element at the end of the list; when visible, request next page
  • Loading indicator and “no more posts” end-cap
  • Basic error state with a retry button for the last failed request

What you’re practicing

  • IntersectionObserver lifecycle and cleanup
  • Minimal pagination state: page, items[], isLoading, hasMore, error

How interviewers evaluate

CategoryWhat to Look For
CorrectnessFetches sequential pages; stops when hasMore=false
State modelingClear separation of in-flight vs accumulated items
UX clarityVisible loading and end-of-feed cues; retry works
CommunicationExplains observer thresholds and cleanup reasoning

Self-check

  • Does the sentinel trigger exactly once per page (no duplicate requests)?
  • Does retry resume correctly after an error without losing already-fetched posts?

Stage 2 — De-duplication and Race Safety

Goal

Ensure items are unique and prevent out-of-order responses from corrupting state.

What to implement

  • De-duplication by id when appending new pages
  • Guard against parallel “next page” requests (e.g., rapid sentinel toggles)
  • Abort or ignore stale fetches when pagination state changes (e.g., filter or feed reset)

Techniques

  • Track an inFlightPage and ignore additional triggers until it resolves
  • Use AbortController to cancel superseded requests
  • Maintain a Set of seen IDs for fast de-dupe

What you’re practicing

  • Concurrency control and idempotent state transitions
  • Robust merges of remote pages into local state

How interviewers evaluate

CategoryWhat to Look For
Async controlNo double-fetch per page; clean aborts on reset
Data integrityNo duplicates; stable order across appends
Edge-case handlingWorks with jittery observer or variable latency
ReasoningCan describe how stale responses are identified and ignored

Self-check

  • If the server returns duplicates across page boundaries, do you ever show repeats?
  • If the sentinel momentarily hides/shows, do you fire again unnecessarily?

Stage 3 — Optimistic Mutations

Goal

Support local interactions (e.g., like/unlike) without waiting for the server, with safe rollback on failure.

What to implement

  • Clicking “like” immediately updates UI (liked state and count)
  • Send mutation request; if it fails, rollback to pre-click state and show an inline, non-blocking error
  • Prevent double-click spamming while a mutation is in-flight per post

What you’re practicing

  • Local optimistic updates and reconciliation with server truth
  • Mutation scoping: per-item in-flight state, not global
  • Error UX that preserves user context

How interviewers evaluate

CategoryWhat to Look For
UX and resilienceFeels instant; failures don’t leave UI corrupted
State isolationOnly the targeted post is “busy”; rest of feed remains interactive
Rollback logicCorrectly returns to previous state on error
CommunicationExplains consistency model: optimistic vs eventual server confirmation

Self-check

  • Simulate a 409/500: does the like revert cleanly and show a clear message?
  • Can you quickly toggle like/unlike without glitches or counter drift?

Stage 4 — Caching, Prefetching, and Performance

Goal

Reduce redundant work, stabilize scrolling, and smooth UX under load.

What to implement (choose several)

  • Cache pages in memory keyed by page and merge by id; on revisiting, render cached instantly
  • Prefetch the next page when the user is within N pixels/one viewport of the bottom (or when the previous page settles)
  • Media performance: loading="lazy", decoding="async", width/height to avoid layout shifts
  • Virtualization for very long feeds; ensure correct item keys and avoid “jumping”
  • Background refresh (stale-while-revalidate): when revisiting page 1, show cached first then refresh silently, reconciling by id without jank
  • Lightweight debug HUD (optional): cache hits/misses, in-flight requests, last latency

What you’re practicing

  • Cache design and reconciliation
  • Prefetch heuristics and bandwidth trade-offs
  • Visual stability and scroll performance under heavy DOM

How interviewers evaluate

CategoryWhat to Look For
ArchitectureCohesive caching layer; clean reconciliation rules
PerformanceObservable improvements; avoids re-render storms
UX smoothnessNo jarring jumps; predictable image loading
Trade-offsAware of memory vs. freshness; explains eviction strategies

Self-check

  • Toggle network throttling: does cached data appear immediately with a later quiet refresh?
  • Does virtualization preserve keyboard focus and scroll position when items change?

Stage 5 — Reliability: Backoff, Recovery, and State Restoration

Goal

Strengthen the feed against flaky networks and app lifecycle changes.

What to implement

  • Retry strategy with exponential backoff and jitter for transient GET failures
  • A global “Refresh” control (or pull-to-refresh on mobile) to revalidate the feed from page 1 without losing scroll context, if feasible
  • Preserve and restore scroll position when navigating away and back (route change → return)
  • Optional: offline read with hydration from local storage for page 1; reconcile on reconnect

What you’re practicing

  • Practical reliability patterns and user recovery flows
  • App lifecycle awareness beyond the happy path

How interviewers evaluate

CategoryWhat to Look For
ResilienceHandles failure modes gracefully without trapping the user
User controlClear manual recovery options
Lifecycle thinkingPosition and feed state survive navigation
CommunicationPrioritizes what matters under real constraints

Self-check

  • Force every third request to fail: do users still make forward progress?
  • After navigating to a detail view and back, does the feed restore where you left off?

Rubric

StageFocusEvaluation EmphasisBar for Senior
1Scroll-triggered paginationCorrect observer use; clear statesSolid baseline with no duplicate loads
2De-dupe & race safetyIdempotent merges; guarded requestsNo duplicates; no stale overwrites
3Optimistic mutationsInstant UX with rollbackLocalized in-flight state; robust rollback
4Cache & performancePrefetching; virtualization; SWRSmooth, measured, maintainable
5Reliability & lifecycleBackoff; restore; manual refreshUsers recover quickly from failures

Senior-level performance typically means: Stage 1–2 are crisp and race-safe; Stage 3 shows confident optimistic updates; Stage 4 introduces a sensible cache/prefetch strategy and media/perf improvements; Stage 5 demonstrates practical reliability and lifecycle handling.

Common Pitfalls

  • Letting multiple “next page” requests proceed concurrently; duplicated or out-of-order pages
  • Appending arrays blindly without de-duplication; visible repeated posts
  • Mutation logic that increments/decrements counters incorrectly on failures
  • Prefetching too aggressively, wasting bandwidth or causing request stampedes
  • Virtualization that breaks accessibility or loses focus/scroll anchors on updates
  • Observer not disconnected on unmount or re-created on every render, causing leaks

Minimal Materials to Start

  • Post type and a mock dataset (200–1,000 posts) with randomized ordering
  • fetchPosts(page, pageSize, { signal }) mock with occasional duplicates, latency, and 429/500 errors
  • likePost(id, like, { signal }) mock with chance of 409/500
  • A React skeleton with a feed component, a useIntersection helper, and a test setup with fake timers and network stubs