Back
Keentune
Web Performance curriculum 19 chapters
·
141 concepts
·
free
Everything the adaptive question bank can teach and test in Web Performance, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
Start practising Web Performance
New here? Read the Web Performance guide
A free 16-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. Why speed matters and what a good metric is
•
LCP measures loading, INP responsiveness, CLS visual stability
•
the questions are "is it happening, useful, usable, delightful", not "how many bytes shipped"
•
each vital has three bands, not one cut-off, and "needs improvement" is not a pass
•
vitals are assessed at the 75th percentile of page loads, so a healthy average can still fail
•
a URL only "passes Core Web Vitals" when LCP, INP and CLS are all in the good band
•
TTFB, FCP and TBT diagnose a Core Web Vital; they are not themselves Core Web Vitals
•
the population you optimize for is a mid-range Android on a slow link, not your laptop on wifi
•
faster pages measurably raise conversion and cut bounce; that is the argument for the work
B. Largest Contentful Paint — what it measures
•
good is ≤2.5s at p75; above 4.0s is poor
•
only <img> , <image> in SVG, video poster, CSS background-image , and block-level text qualify
•
the candidate is re-elected as content paints, and freezes at the first scroll, tap or keypress
•
the reported size is the smaller of on-screen and intrinsic size, and clipped or overflowing area does not count
•
FCP is the first pixel of any content; LCP is the paint of the largest one
•
a text LCP has no resource to fetch, so its time is font and render delay, not bytes
•
a client-side route change is not a new page load, so it produces no new LCP
C. Diagnosing and fixing LCP
•
LCP = TTFB + resource load delay + resource load duration + element render delay
•
a healthy split is roughly 40% TTFB, under 10% load delay, 40% load duration, under 10% render delay
•
load delay is usually the hero being injected by JS instead of sitting in the initial HTML
•
loading="lazy" on the LCP image delays it until after layout, costing hundreds of ms
•
fetchpriority="high" promotes the hero out of the browser's default low image priority
•
<link rel=preload as=image imagesrcset imagesizes> preloads the right candidate of a responsive hero
•
the image can be in memory and still not painted while CSS or a blocking script runs
D. Cumulative Layout Shift — what it measures
•
good is ≤0.1 at p75; above 0.25 is poor, and the score is unitless
•
one shift scores impact fraction × distance fraction, both relative to the viewport
•
CLS is the worst 5-second burst of shifts (gaps under 1s), not the sum of every shift
•
a shift within 500ms of a user input is expected and excluded via hadRecentInput
•
moving an element with transform changes no layout position, so it scores zero
•
an element appearing is not a shift; the already-visible content it pushes down is
•
CLS keeps accumulating after load, so a shift on scroll or on an ad refresh still counts
E. Preventing layout shift
•
width and height on <img> let the browser reserve the box before the bytes arrive
•
CSS aspect-ratio reserves the box for fluid media whose pixel size is unknown
•
size a slot from the historical p-something of that slot, and never collapse it when the ad fails
•
a cookie or promo bar inserted above existing content shifts the whole page; overlay or pre-reserve it
•
swapping from fallback to webfont reflows text whenever the two fonts' metrics differ
•
animating top /width shifts layout every frame; transform does not
•
give a lazily-loaded or hydrated component a min-height so its arrival changes nothing
F. Interaction to Next Paint
•
good is ≤200ms at p75; above 500ms is poor
•
clicks, taps and key presses count; scrolling and hovering do not
•
input delay, then event processing, then presentation delay before the next frame
•
INP reports the page's worst interaction, discarding one outlier per 50 interactions
•
the clock stops at the painted frame, so a fast handler that queues heavy rendering still scores badly
•
one tap fires pointerdown, pointerup and click; the interaction's latency is the longest of the group
•
INP replaced FID in March 2024 because FID only measured the first interaction's input delay
•
a page nobody interacts with reports no INP at all, which is not the same as a good INP
G. Long tasks and the main thread
•
any uninterrupted main-thread task over 50ms is a long task, and input cannot be handled during it
•
TBT sums each long task's time beyond 50ms; it is the lab proxy for INP
•
a setTimeout(…, 0) breaks work into separate tasks so pending input can run between them
•
scheduler.yield() yields but resumes ahead of other queued tasks, unlike setTimeout
•
moving non-visual work until after the next frame improves INP without doing less work
•
debounce runs once after the input settles; throttle runs at a fixed rate during it
•
a worker runs off the main thread but cannot touch the DOM, and postMessage copies data
•
a growing heap lengthens garbage-collection pauses, and a GC pause is a main-thread stall that surfaces as an INP spike
H. The cost of JavaScript
•
JS is the most expensive byte type because it is downloaded, parsed, compiled and run
•
the network cost tracks the compressed size, but parse and execute track the uncompressed size
•
dynamic import() defers a route's or a widget's code until it is actually needed
•
dead-code elimination needs static ES modules and honest sideEffects metadata
•
compiling to an ancient target ships polyfills and slower output to modern browsers
•
hydration re-runs the component tree on the client, which is main-thread work no server render removes
•
one giant bundle invalidates the whole cache on any change; too many chunks cost requests
I. Script loading and third parties
•
a plain <script> stops HTML parsing until it downloads and executes
•
defer keeps document order and runs before DOMContentLoaded; async runs as soon as it lands, in any order
•
type="module" is deferred by default, so adding defer to it changes nothing
•
a secondary parser fetches subresources ahead of the parser, and JS-injected tags are invisible to it
•
a vendor tag runs on your main thread at your priority with no size limit you control
•
replace a heavy embed with a static placeholder that loads the real thing on interaction
J. The network path to the first byte
•
good TTFB is ≤0.8s; it is a diagnostic for LCP, not a Core Web Vital itself
•
redirect time, DNS, connection, TLS, request, and the server's own think time
•
every redirect costs a full round trip, and a chain multiplies it before any HTML arrives
•
DNS plus TCP plus TLS is roughly three round trips before the first request is even sent
•
HTTP/2 interleaves many streams on one connection, removing the six-connection limit and domain sharding
•
HTTP/3 over QUIC ends TCP head-of-line blocking and can resume a connection in 0-RTT
•
Brotli beats gzip on text; re-compressing JPEG, WebP or a zip gains nothing
K. Resource hints and priority
•
opens DNS, TCP and TLS to an origin early; each one costs, so use it for a handful of critical origins
•
resolves DNS only, so it is far cheaper than preconnect and safe to fan out wider
•
rel=preload without a correct as gets the wrong priority and can download the file twice
•
a preload nothing consumes wastes bandwidth and logs a console warning
•
rel=prefetch is a lowest-priority fetch for a page the user will probably visit next
•
preloads and pre-parses an ES module plus its dependency graph, which plain preload does not
•
high /low /auto nudge a resource within its own class rather than jumping it above CSS
•
CSS and blocking scripts load highest, images start low and are promoted once layout proves they are visible
•
AVIF is usually smallest, WebP is the safe default, and JPEG/PNG are the fallbacks
•
w descriptors plus sizes let the browser pick by layout width and device pixel ratio
•
a sizes value that does not match the CSS layout makes the browser pick the wrong candidate
•
<picture> with type gives format fallback, and with media gives real art direction
•
loading="lazy" only helps off-screen images, and hurts anything above the fold
•
a huge image costs main-thread decode time even after it downloads; decoding="async" moves it off the critical path
•
images and video are the largest share of transferred bytes on the median page, which is why image work is the highest-leverage saving
•
a base64 data URI removes a request and adds about a third to the bytes, in a file that must parse before render, and it cannot be cached separately
•
preload="none" plus a poster keeps an unplayed video from downloading megabytes
•
block , swap , fallback and optional differ only in how long text is invisible and how long a swap stays allowed
•
FOIT hides text until the font loads; FOUT shows a fallback and swaps, trading a blank screen for a shift
•
a font is only requested after CSS parses and a matching element exists, which is why the critical one is preloaded
•
unicode-range splits a family so a script's glyph file is only fetched when those characters appear
•
WOFF2 is the format to ship, and one variable font can replace a stack of weight files
•
size-adjust , ascent-override and friends make the fallback occupy the same space, removing the swap shift
•
a font CDN costs an extra connection, and cache partitioning killed the cross-site reuse that justified it
N. CSS and the critical rendering path
•
the browser needs both the DOM and the CSSOM before it can build a render tree and paint
•
a stylesheet in the head blocks the first paint by default, which is why one slow CSS file blanks the page
•
a media that does not match makes the sheet non-render-blocking, though it is still downloaded
•
@import inside CSS is only discovered after the parent sheet parses, adding a serial round trip
•
inlining above-the-fold rules and deferring the rest removes the blocking request entirely
•
rules that never match still cost download, parse and style recalculation on every change
•
style, layout and memory all scale with node count, so a huge DOM is itself a performance bug
O. Rendering: frames, layout and compositing
•
60Hz gives ~16.7ms per frame, and browser overhead leaves roughly 10ms for your code
•
JS, style, layout, paint, composite — and each step you skip is time you keep
•
geometry properties relayout, colors repaint, and transform /opacity only composite
•
reading offsetHeight after a style write forces layout immediately, before the frame needs it
•
alternating reads and writes in a loop forces one layout per iteration; batch reads, then writes
•
it promotes an element to its own layer, and too many layers cost GPU memory instead of saving time
•
auto skips rendering off-screen subtrees, and contain-intrinsic-size keeps the scrollbar honest
•
contain: layout paint promises the engine an element's layout cannot affect outside geometry and its painting cannot escape its box
•
scrolling runs on the compositor until a non-passive wheel or touch listener forces the main thread to be consulted on every gesture
P. HTTP caching, CDNs and the service worker
•
Cache-Control: max-age is freshness in seconds counted from when the response was generated
•
no-cache stores the response but revalidates before reuse; no-store never writes it down at all
•
tells the browser not to revalidate a still-fresh response even when the user reloads
•
a content hash in the URL plus a one-year max-age, immutable gives safe permanent caching
•
ETag or Last-Modified turns a re-fetch into a bodyless 304, which still costs a full round trip
•
s-maxage and private control the CDN separately from the browser cache
•
serves the stale copy instantly and refreshes it in the background, hiding the revalidation latency
•
the named request headers join the cache key, so Vary: User-Agent effectively disables caching
•
the memory cache reuses resources from RAM for the current session; the disk cache is slower but survives a restart
•
Cache Storage is app-controlled and ignores HTTP freshness, so a stale service worker can pin an old build
Q. Speculative loading and the back/forward cache
•
a JSON speculationrules script declares URLs to prefetch or prerender, with an eagerness level
•
prerendering executes the next page in a hidden tab, so activation is near-instant
•
analytics and other side effects fire early unless the page defers them until activation
•
back/forward can restore a frozen page from memory, JS heap intact, firing pageshow with persisted
•
an unload listener or Cache-Control: no-store on the document disqualifies the page
•
a 103 response sends Link headers so the browser preloads while the server is still building the HTML
R. Measuring: APIs, lab and field
•
buffered: true replays entries that fired before your observer existed
•
the document's own waterfall: redirect, DNS, connect, request, response, DOM and load milestones
•
cross-origin resource timings are zeroed unless the server sends Timing-Allow-Origin
•
vitals accrue for the whole page lifetime, so a RUM beacon must fire on hide, not on load
•
CrUX is a 28-day rolling p75 from opted-in Chrome users, and is what Search reads
•
a single throttled lab run, weighted mostly by TBT, LCP and CLS, with no INP in it
•
device, network, cache state and real interaction make lab and field disagree by design
•
LoAF and the attribution build name the offending element and script instead of just the number
S. Budgets and regression discipline
•
milestone timings, quantity budgets (bytes and requests), and rule-based scores each catch different regressions
•
set the number from your current p75 and the fastest competitor, not from a round figure
•
a budget that fails the build stops regressions; a budget in a spreadsheet documents them
•
synthetic runs catch bundle regressions per PR, field data confirms real users felt it
•
tie a speed change to the conversion or engagement metric it claims to move, or it is unproven
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
Start practising Web Performance
All about Web Performance practice
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC