cf.completefrontend
My progress
FUNDAMENTALS · MODULE 1

Back-of-the-envelope numbers for frontend

Quick, rough math with round numbers (bytes over the network, CPU time on a cheap phone, memory in a long-lived tab, and requests per second at the server) that turns "this might be slow" into a reason to pick one design over another.

Why a frontend engineer estimates

Two candidates get the same question: design an infinite news feed. Both say they will use a virtualized list, which only keeps the visible rows in the page. The interviewer asks each of them why.

The first says: "Long lists can be slow." The second says: "After an hour of scrolling a user can reach 1,000 posts. At about 30 DOM nodes per post that is 30,000 nodes, far past the roughly 1,400 that Lighthouse long treated as excessive. With virtualization we keep about 20 posts mounted, so around 600 nodes, no matter how far they scroll."

Both picked the same answer. Only the second one gave a reason the interviewer can check. That is what back-of-the-envelope estimation is: quick, rough math done out loud with round numbers, used to choose between options. The name comes from doing the sums on the back of an envelope: no calculator, no spreadsheet, a minute at most.

Two candidates choose a virtualized list. On the left, the reason is a guess: long lists can be slow, and the interviewer is left asking how slow and why. On the right, the reason is a number: 1,000 posts times 30 nodes is 30,000 DOM nodes, far past the 1,400 guideline, so virtualize and keep about 600 nodes.a guessa numbercandidateLong lists canbe slow.how slow? why?nothing to check1,000 posts x 30 nodes= 30,000 DOM nodesguideline: about 1,400virtualize: about 600a reason with a sizethe interviewer can push on it
Same decision, different signal. The number on the right can be checked, challenged, and resized; the guess on the left can only be accepted or doubted.

Frontend estimation is not backend estimation

Backend system design interviews estimate storage, servers, and database writes: how many terabytes a year, how many machines. Frontend interviews almost never need those. Your constraints live somewhere else: on the user's device and on the link between the device and your servers.

  • Bytes over a slow network. A phone on a weak 4G signal downloads slowly and waits a long time for each round trip.
  • CPU time on a cheap phone. JavaScript has to be parsed, compiled, and run. A budget Android phone does that several times slower than a developer laptop.
  • Memory in a tab that stays open. Chat apps, dashboards, and editors stay open for hours. DOM nodes, decoded images, and cached data add up until the tab is killed or gets sluggish.
  • Requests your clients send. A million phones that each poll every few seconds are a load test you run on your own servers, every day.

Each of those four has a few numbers worth knowing by heart, and a one-line formula that turns them into an answer. This lesson gives you both, with the source for each number or a clear note when it is only a rule of thumb.

What a number buys you

A number does three jobs in an interview. It justifies a choice ("virtualize, because 30,000 nodes"). It sizes a choice ("keep 20 rows mounted, not 200"). And it rules out a choice early, so you do not waste ten minutes on it ("polling every second from 500,000 open tabs is 500,000 requests per second: no").

It does not need to be precise. Being within a factor of two or three is enough, because the decisions it drives are usually between options that differ by ten times or more. Nobody will mark you down for saying 4 MB when the real figure is 3.7 MB. They will notice if you never put a number on anything.

Check your understanding · Exercise 1

Which of these is a useful back-of-the-envelope statement in a frontend design interview? Pick one.

The mental model: four budgets and a ruler

Think of a household budget. You have a few accounts (rent, food, travel), each with a limit. You don't track every coin; you ask whether a big purchase fits in the account it comes from. Frontend estimation works the same way, with four accounts.

The user's phone holds three budgets: CPU, measured in milliseconds of main-thread work; memory, in megabytes held by the tab; and, on its link to the internet, the network budget, in kilobytes and round trips. Your servers hold the fourth budget: requests per second. Arrows run from the phone over the network to the servers.the user's phoneCPUms of main-thread workMemoryMB held by the tabthe link in betweenNetworkKB and round tripsServer loadrps on your servers
Three of the four budgets are spent on hardware you don't control. Only the server budget shows up on your own bill, which is why it is the one teams notice first and the device budgets are the ones they forget.
The four budgets. The first three are spent on the user's device; only the last one shows up on your own servers.
BudgetUnitWho paysTypical limit to compare against
NetworkKB transferred, round tripsThe user, waitingLCP within 2.5 s on Lighthouse's slow 4G profile
CPUms of main-thread workThe user, on their phone16.7 ms per frame, 50 ms per task, 200 ms per interaction
MemoryMB in the tabThe user's deviceA few hundred MB before a cheap phone starts killing tabs (rule of thumb)
ServerRequests per second, MB per secondYour companyWhatever the backend and CDN are sized for

Every design choice spends from one or more of these accounts. A big JavaScript library spends network (to download it) and CPU (to run it). A long list spends memory (DOM nodes) and CPU (layout of all those nodes). Polling spends server budget and a little battery.

Naming the account is half the estimate: it tells you which limit to compare against.

Think in orders of magnitude

An order of magnitude is a factor of ten. The useful question is rarely "is it 120 ms or 150 ms?" but "is it 1 ms, 10 ms, 100 ms, or 1 second?". Those are different worlds.

1 ms is free. 10 ms fits in a frame. 100 ms is the edge of feeling instant. 1 second is a visible wait.

So round every input to one or two significant figures before you multiply. 86,400 seconds in a day becomes 100,000. 1,024 bytes in a kilobyte becomes 1,000. 1.6 Mbps stays 1.6 or becomes 2.

The answer will be off by 10 to 20 percent and you will get it in your head in five seconds, which is the trade you want.

The units that trip people up

  • Bits and bytes. Network speeds are quoted in bits per second (Mbps). File sizes are in bytes (KB, MB). One byte is 8 bits. A 1 MB file on a 1 Mbps link takes 8 seconds, not 1.
  • Transferred and decoded. A file has a size on the wire (compressed) and a size once the browser unpacks it. For JavaScript that is roughly 3 to 4 times bigger; for a JPEG the decoded pixels are often 20 times bigger or more. Always say which size you mean.
  • CSS pixels and device pixels. Layout uses CSS pixels. A phone with a device pixel ratio (DPR) of 3 draws 3 by 3 device pixels for every CSS pixel, so an image needs 9 times the pixels to look sharp.
  • Average and peak. Traffic per day divided by seconds per day gives the average. Servers must survive the peak, which is usually a few times higher.

Every estimate has the same shape

Almost every frontend estimate is one multiplication and one comparison: count times size per item, compared with a limit. 1,000 posts times 30 nodes per post, compared with 1,400 nodes. 200 thumbnails times 150 KB of pixels, compared with a few hundred MB. 100,000 tabs times one request per 10 seconds, compared with what the API can serve.

text
total   = count x size per item
verdict = total compared with the budget's limit

nodes   = 1,000 posts x 30 nodes        = 30,000   (guideline: about 1,400)
pixels  = 200 avatars x 150 KB decoded  = 30 MB    (limit: a few hundred MB)
rps     = 100,000 tabs / 10 s           = 10,000/s (limit: the API's capacity)
Three different questions, one shape. If you remember only this, you can estimate almost anything a frontend interview asks.

Check your understanding · Exercise 2

A 500 KB compressed bundle is downloaded over a 5 Mbps connection. Roughly how long does the transfer take, ignoring round trips? Pick one.

Latency numbers every frontend engineer should know

Time is the budget users feel. The numbers below span six orders of magnitude, from a lookup in memory to a page load on a slow phone. You do not need them to the millisecond. You need to know which row a thing belongs to.

Bars on a log scale from 0.1 microseconds to 10 seconds. A Map lookup takes 0.5 microseconds, a disk cache read about 5 ms, one frame 16.7 ms, a round trip to a CDN edge about 20 ms, New York to London about 75 ms, the Lighthouse mobile round trip 150 ms, a good INP 200 ms, connecting and sending the first request on that profile 600 ms, running 1 MB of JavaScript on a mid-range phone about 1 second, and a good LCP 2.5 seconds.Map lookup0.5 µsdisk cache read5 msone frame at 60 Hz16.7 mstrip to a CDN edge20 msNew York to London75 msLighthouse mobile RTT150 msINP, good200 msconnect + first request600 ms1 MB of JS, mid phone1 sLCP, good2.5 s1 µs1 ms1 s
Each tick is a thousand times the one before. A memory lookup is free, the network lives around 100 ms, and big JavaScript on a phone costs whole seconds. Grey bars are targets, not costs.
Rows marked rule of thumb vary a lot by device and network; the others are definitions or published thresholds.
WhatTypical timeWhere the number comes from
Read a value from a JavaScript MapWell under 1 microsecondRule of thumb
Read a file from the HTTP disk cacheA few ms, more on slow phonesRule of thumb; check the Timing tab in DevTools
One frame at 60 Hz16.7 ms1,000 ms divided by 60
A long taskOver 50 msW3C Long Tasks API definition
Interaction to Next Paint (INP), good200 ms or lessweb.dev Core Web Vitals thresholds
Round trip to a nearby CDN edge10 to 30 ms on a good connectionRule of thumb
Round trip New York to LondonAbout 56 ms at the speed of light in fibre, 70 to 80 ms in practicePhysics, plus routing overhead
Round trip on Lighthouse's mobile profile150 msLighthouse throttling docs
Largest Contentful Paint (LCP), good2.5 s or lessweb.dev Core Web Vitals thresholds

Round-trip time and bandwidth are different things

Round-trip time (RTT) is how long a tiny message takes to go to the server and back. Bandwidth is how many bits per second the link can carry once data is flowing. A good analogy is a motorway: RTT is the length of the road, bandwidth is the number of lanes. More lanes do not make the trip shorter.

RTT is set mostly by distance and by the radio link. Light in optical fibre travels at about 200,000 km per second, two thirds of its speed in a vacuum. New York to London is about 5,600 km, so a round trip is at least 11,200 km divided by 200,000, which is 56 ms, before any routers, queues, or mobile radio add their share. No upgrade fixes physics: only moving the server closer does, which is what a CDN (content delivery network: servers spread around the world) is for.

Small requests are almost pure RTT. A 2 KB JSON response on a 150 ms link takes about 150 ms whether the bandwidth is 2 Mbps or 200 Mbps. Big files are mostly bandwidth. So for many small requests, count round trips; for a few big files, count bytes.

The network profiles to quote

"Mobile" covers everything from 5G next to a mast to one bar of signal on a train. When you estimate, pick a named profile so the interviewer knows what you assumed. The safest to quote is the one Lighthouse and PageSpeed Insights use by default, because it is published and stable.

Lighthouse says its mobile profile is roughly the bottom quarter of 4G connections and the top quarter of 3G ones. Treat it as a realistic slow user, not a worst case. The DevTools rows show the round trip each preset aims to mimic.
ProfileRound tripDownloadUploadCPU
Lighthouse mobile ("Slow 4G")150 ms1.6 Mbps750 Kbps4x slowdown
Chrome DevTools "Fast 4G" preset60 ms9 Mbps1.5 MbpsSet separately
Chrome DevTools "3G" preset400 ms500 Kbps500 KbpsSet separately
Lighthouse desktop40 ms10 MbpsNot modelledNo slowdown
Typical home broadband (rule of thumb)10 to 30 ms50 to 500 Mbps10 to 100 MbpsFast laptop

Chrome DevTools offers three presets: "Slow 4G" (the same targets as Lighthouse mobile), "Fast 4G", and "3G". DevTools throttles each request rather than each packet. To compensate, each preset adds a multiple of its target round trip as latency per request: about 560 ms for Slow 4G, 165 ms for Fast 4G, and 2,000 ms for 3G. It also trims the throughput by 10 to 20 percent.

So do not read the DevTools latency figure as an RTT; use the targets in the table. Real 3G round trips are often 200 to 400 ms or worse (rule of thumb), and 5G or good Wi-Fi can be under 20 ms.

A new connection costs several round trips

Before the first byte of a page arrives from a new origin, the browser has to find it and open a secure connection. Each step is at least one round trip, and nothing useful happens while they run.

Sequence diagram of a first visit. The browser asks a DNS resolver for shop.com and gets an IP address: one round trip. It sends a TCP SYN to the server and gets SYN-ACK: a second round trip. It sends a TLS ClientHello and gets the ServerHello: a third. Then it sends GET slash and the first 14 KB of HTML comes back: a fourth. Four round trips of 150 ms is 600 ms.browserDNS resolverservershop.com?203.0.113.7TCP SYNSYN-ACKTLS hellokeys agreedGET /first 14 KB of HTMLRTT 1: DNSRTT 2: TCPRTT 3: TLS 1.3RTT 4: request4 round trips x 150 ms = 600 ms before any HTML
Every arrow pair is one round trip, and none of them carries page content until the last. HTTP/3 merges the TCP and TLS steps; preconnect starts them early for an origin you know you will need.
  1. DNS lookup: turn the host name into an IP address. One round trip to a resolver, often cached.
  2. TCP handshake: open the connection. One round trip.
  3. TLS 1.3 handshake: agree on encryption keys. One round trip (TLS 1.2 needed two).
  4. The HTTP request and response: one more round trip, and the first part of the response arrives.

That is four round trips: 600 ms on the 150 ms profile before the HTML even starts to arrive. HTTP/3 runs over QUIC, a newer transport protocol that merges the TCP and TLS steps into one round trip, saving one. Reusing an open connection saves all but the last. This is why a page that pulls fonts, scripts, and images from five different origins pays a hidden cost that a byte count never shows.

One more number belongs here: TCP does not send at full speed at first. Servers commonly start with a congestion window of 10 packets (RFC 6928), about 14 KB, and roughly double it each round trip. So the first 14 KB of HTML arrives in the first round trip, and a 100 KB file needs about three or four round trips even on a fast link. This is the origin of the advice to fit critical HTML and CSS into the first 14 KB.

Check your understanding · Exercise 3

A page needs a new HTTPS connection (DNS, TCP, TLS 1.3) and then a 10 KB HTML response, on the 150 ms, 1.6 Mbps profile. Roughly when does the HTML finish arriving? Pick one.

JavaScript: two sizes, two costs

JavaScript is the most expensive byte on the web. An image of 300 KB costs download time and then gets decoded, mostly off the main thread. A script of 300 KB costs the same download time, and then the engine has to parse it, compile it, and run it. Much of that work happens on the main thread, while the page cannot respond to taps.

Transfer size and parsed size

Every script has two sizes. The transfer size is the compressed bytes on the wire: it sets download time. The parsed size (also called the uncompressed or resource size) is what the JavaScript engine has to read: it is the better guide to CPU time. Minified JavaScript usually compresses 3 to 4 times with gzip or Brotli (rule of thumb; check your own build output), so 300 KB on the wire is roughly 1 MB in the engine.

Timeline of a 300 KB compressed bundle, about 1 MB parsed. On a laptop on Wi-Fi: connecting takes 0.08 s, downloading 0.05 s, and parsing and running about 0.26 s, about 0.4 s in all. On a mid-range phone on the Lighthouse mobile profile: connecting takes 0.6 s, downloading 1.5 s, and parsing and running about 1.1 s, about 3.2 s, past the 2.5 s LCP line.LCP limit 2.5 slaptop, Wi-Fi0.4 sphone, slow 4G3.2 s0 s1 s2 s3 s4 sconnectdownload 300 KBparse and run 1 MB
Same file, roughly eight times the cost. Download time comes from the compressed size and the network; CPU time comes from the parsed size and the device, and compression does nothing for it.

Bundlers print both numbers. When someone quotes a library's size, ask which one. A library listed as "40 KB gzipped" is about 140 KB of code to parse; listed as "140 KB" it might be only 40 KB to download. Mixing the two is the most common estimation slip in performance discussions.

What is normal today

The HTTP Archive crawls millions of sites every month, and its yearly report, the Web Almanac, analyses that data. The Page Weight chapter of the 2025 edition (data from July 2025) is the best source for "what does a typical page ship":

From the HTTP Archive Web Almanac 2025, Page Weight chapter. These are transfer sizes. Inner pages carry far fewer image bytes and about the same JavaScript.
Median page, July 2025MobileDesktop
Total home page weight2,559 KB2,862 KB
Images (home page)911 KB1,058 KB
JavaScript (home page)632 KB697 KB
Requests (all pages)7277

Over 600 KB of compressed JavaScript is the median, not a heavy outlier. Alex Russell's "Performance Inequality Gap, 2024" suggests at most about 365 KB of compressed JavaScript (out of about 730 KB in total) for a JavaScript-heavy page that should be interactive within 3 seconds on 75th percentile devices and networks. So the median page is already over a reasonable budget, and quoting a budget in an interview is a signal of care.

CPU time: the cost you don't see on a laptop

The parsed bytes then cost CPU. How much depends heavily on the device. A fast developer laptop gets through 1 MB of unzipped JavaScript in roughly a quarter of a second of main-thread work (rule of thumb).

Lighthouse slows the CPU by 4x for its mobile run to emulate a mid-tier phone. The V8 team measured a median phone (a Moto G4) taking 3 to 4 times longer than a high-end phone to run Reddit's JavaScript, and a low-end phone over 6 times longer.

There is no single published constant, so use a rule of thumb and say it is one: about 1 second of main-thread work (parse, compile, and run) per megabyte of unzipped JavaScript on a mid-range phone. That is the laptop figure times Lighthouse's 4x; a laptop needs about a quarter of it and a budget phone about twice it. Treat it as right within a factor of two, because the real figure depends heavily on how much of the code runs at startup.

The V8 team's "The cost of JavaScript in 2019" explains where the time goes. Modern engines parse and compile much of a script off the main thread while it downloads, so download and execution are now the main costs. Execution happens on the main thread, and a slow CPU stretches it.

ts
// The full cost of a 300 KB (compressed) bundle on the Lighthouse mobile profile
const transferKB = 300;
const parsedKB = transferKB * 3.5;           // about 1 MB for the engine
const downloadS = (transferKB * 8) / 1600;   // 1.6 Mbps: about 1.5 s
const roundTripsS = 4 * 0.15;                // new connection: about 0.6 s
const cpuS = (parsedKB / 1000) * 1;          // mid-range phone: about 1 s
// total before the app is interactive: roughly 3.2 s
Five lines of arithmetic you can say out loud. Every constant is either published (150 ms, 1.6 Mbps) or a named rule of thumb (3.5x, 1 s per MB).

The same 300 KB on a laptop with broadband costs about 0.4 seconds in total. Same code, roughly eight times the cost. When the interviewer says "users are global" or "emerging markets", this gap is what they want you to see.

Where the number leads

  • Split the bundle. Load the code for the first screen first; load the editor, the chart library, and the settings page when they are needed. The code-splitting lesson covers how.
  • Set a budget. "Under 150 KB compressed for the first route" is concrete and checkable in CI.
  • Weigh each dependency. A 70 KB date library to format one date is a bad trade; Intl.DateTimeFormat is free.
  • Move work off the main thread. Parsing a big JSON file or computing a diff can go to a Web Worker.

Check your understanding · Exercise 4

A charting library is listed as 120 KB minified and gzipped. Using the rules of thumb in this chapter, what does adding it to the first load cost on a mid-range phone on the Lighthouse mobile profile? Pick one.

Images: small files, big memory

Images are the largest share of bytes on most home pages: 911 KB on the median mobile home page in the 2025 Web Almanac. Per byte they are cheaper than JavaScript, because decoding happens mostly off the main thread. But they have a hidden cost that catches almost everyone: the memory they take once decoded.

File weights to remember

These are rules of thumb for photos at reasonable quality. Graphics with flat colours, screenshots, and text compress very differently, so treat the table as a starting point, not a fact.

Rules of thumb. Google's WebP study found lossy WebP files 25 to 34 percent smaller than JPEGs of similar quality; AVIF is often around half the JPEG size, but results vary by image.
ImagePixelsJPEGWebPAVIF
Avatar or thumbnail200 x 20010 to 20 KB8 to 15 KB5 to 10 KB
Feed photo on a phone1080 x 1080100 to 250 KB70 to 180 KB50 to 120 KB
Desktop hero1920 x 1080200 to 400 KB150 to 300 KB100 to 200 KB
Original from a phone camera4000 x 3000 (12 MP)2 to 5 MBNot served as isNot served as is

The last row is the one that hurts. User-generated content (a marketplace listing, a profile photo, a chat attachment) often arrives as a 4 MB original. Serving it without resizing puts megabytes on a mobile link for an image shown 300 pixels wide.

Decoded size: width times height times 4

To draw an image, the browser decodes it into a bitmap: raw pixels, usually 4 bytes each (red, green, blue, and alpha for transparency). The file format no longer matters at that point. A 15 KB AVIF and a 200 KB JPEG of the same dimensions take the same memory once decoded.

An 80 KB photo.avif file, 1,080 by 1,080 pixels, is decoded into a grid of pixels. Each pixel takes 4 bytes: red, green, blue, and alpha. 1,080 times 1,080 times 4 is about 4.7 MB of memory, the same whether the file was JPEG, WebP, or AVIF.photo.avif80 KB filedecode1,080 x 1,080 pixelsone pixelRGBA4 bytes= 4.7 MB decodedthe same 4.7 MB whether the file is JPEG, WebP, or AVIF
The file size is what crosses the network; the bitmap is what the tab holds while the image is shown. Shrinking the file with a better format does not shrink the bitmap: only fewer pixels do.
text
decoded bytes = width x height x 4

200 x 200      x 4 =   160 KB   (a thumbnail)
1080 x 1080    x 4 =   4.7 MB   (a feed photo)
4000 x 3000    x 4 =    48 MB   (a 12 MP original)
The rule of thumb for decoded memory. Browsers may use other pixel formats in some cases, but 4 bytes per pixel is the right planning number.

A feed that keeps 50 feed photos decoded holds about 235 MB of bitmaps. On a phone with 3 or 4 GB of RAM shared with the system and other apps, that is the kind of number that gets a tab reloaded or killed. Browsers can discard decoded images that are far off screen, but you should not rely on that alone in a feed that grows without end.

Device pixel ratio multiplies everything

Most phones have a device pixel ratio (DPR) of 2 or 3. To look sharp, an image shown 120 CSS pixels wide needs 240 or 360 real pixels. Area grows with the square: at DPR 3 the image has 9 times the pixels of the CSS size, and 9 times the decoded memory.

  • 120 by 120 CSS pixels at DPR 3 is 360 by 360, about 0.5 MB decoded.
  • A grid of 300 such thumbnails is about 150 MB decoded if they all stay in the page.
  • Capping the served density at 2x (many sites do, because few people can see the difference) cuts that to about 70 MB.

The fixes follow from the formula: serve the size you show with srcset and sizes, use loading="lazy" for images below the first screen, cap density where the extra sharpness is not worth the cost, and virtualize long grids so off-screen images leave the page.

Check your understanding · Exercise 5

A gallery shows 40 images at 400 by 300 CSS pixels on a DPR 2 laptop, all served at exactly that density. Roughly how much decoded image memory is that? Pick one.

Lists, payloads, and DOM nodes

Lists are where frontend designs most often break at scale: feeds, tables, chat histories, search results, file browsers. For any list, estimate two separate things: the data (bytes to fetch and keep) and the DOM (elements to render). They have very different limits, and mixing them up leads to wrong answers.

Data is usually cheap

Rules of thumb before compression. JSON with repeated keys compresses well, often 5 to 10 times, so a page of 20 posts is usually a few KB on the wire.
Item as JSONSize per itemPage of 2010,000 items
Contact (name, email, avatar URL)About 200 bytes4 KB2 MB
Feed post (text, author, counts, media URLs)1 to 2 KB20 to 40 KB10 to 20 MB
Chat message300 to 500 bytes6 to 10 KB3 to 5 MB
Table row with 15 columns500 bytes to 1 KB10 to 20 KB5 to 10 MB

Even 10,000 contacts is only 2 MB of JSON. That fits easily in memory, which means client-side search and sort over a full contact list is fine. Once parsed into JavaScript objects it grows (objects carry overhead per property; budget 2 to 3 times the JSON size as a rule of thumb), but it is still small. Data is rarely the limit; rendering is.

The DOM is expensive

For years, Lighthouse's "Avoid an excessive DOM size" audit gave the numbers people quote: a warning above about 800 elements in the page body and a failure above about 1,400. Lighthouse 13 (October 2025) replaced it with an "Optimize DOM size" insight, which fails only when a large style or layout update takes more than 40 ms.

So treat 1,400 as a handy guideline, not a hard limit. A few thousand simple elements are usually fine; tens of thousands make every update slow. A big DOM costs in three ways:

  • Memory. Each element holds its attributes, computed style, layout box, and event listeners, plus your framework's objects for it (React keeps a fiber, its internal record, for each component and element). There is no published constant; as a rule of thumb, budget 0.5 to 1 KB per element all in, and measure with a heap snapshot in DevTools.
  • Style and layout time. When something changes, the browser may recompute styles and layout for many elements. The work grows with the size of the tree, so every interaction gets slower as the list grows.
  • Initial render. Creating 10,000 rows takes hundreds of milliseconds of main-thread time on a phone (rule of thumb), even before any images load.
Elements per item:
1,000 items with 40 elements each, all rendered: 40,000 DOM elements, roughly 20 MB to 40 MB of memory by the 0.5 to 1 KB rule of thumb. Lighthouse's older DOM size audit warned above 800 and failed above 1,400.800: warn1,400: guideline101001k10k100k1M40,000 elements, about 20 MB to 40 MB
The scale is logarithmic: each tick is ten times the last. The dashed lines are the older Lighthouse thresholds, kept as guidelines. Without virtualization the bar grows with the list; with it, the bar stops at the rows that fit on screen, here 13 (an 800 px screen of 120 px rows plus overscan).

Do the math on a feed. A post card is often 30 to 60 elements once you count the avatar, name, time, text, media, and the row of action buttons with their icons. After a long session of 1,000 posts at 40 elements each, the page holds 40,000 elements: about 30 times the 1,400 guideline and, at 1 KB each, around 40 MB for the DOM alone, before images.

Virtualization math

Virtualization (also called windowing) renders only the rows near the viewport and recycles them as the user scrolls. The number of mounted rows depends on the viewport, not the list:

text
mounted rows = ceil(viewport height / row height) + overscan

phone:   ceil(800 / 120) + 2 x 3 =  7 + 6 = 13 cards
nodes:   13 cards x 40 elements  = 520 elements

table:   ceil(900 / 40)  + 2 x 5 = 23 + 10 = 33 rows
nodes:   33 rows x 16 cells      = about 530 elements
Overscan is the extra rows kept above and below the screen so fast scrolling doesn't show blank space. The total stays flat whether the list has 100 or 100,000 items.

So the decision rule writes itself. Under a few hundred simple rows, render them all; the complexity of virtualization is not worth it. Over a thousand, or with rich rows, or with a list that grows without end, virtualize. Between the two, estimate the element count and compare it with the 1,400 guideline.

Check your understanding · Exercise 6

An admin table shows 5,000 rows of 12 columns with no virtualization. Each cell is one element plus a wrapper per row. Which statement is right? Pick one.

Requests per second from N users

The first three budgets live on the user's device. The fourth lives on your servers. A frontend design decides how often clients talk to the backend, and at scale that choice can cost more than any other. Estimating it takes one formula.

text
requests per second = users x requests per user / seconds in the window

average over a day:  1,000,000 users x 50 requests / 100,000 s = 500 rps
peak (x3):           about 1,500 rps
A day is 86,400 seconds; round it to 100,000. The peak factor is a rule of thumb: daily traffic is uneven, and launches, sales, and live events can be far spikier.
Flowchart from 1,000,000 daily users. The upper path, for things done once per visit: times 50 calls divided by 100,000 seconds is 500 requests per second on average, and about 1,500 at a peak of three times. The lower path, for things done every few seconds while the app is open: 20 minute sessions out of 1,440 minutes a day gives about 14,000 users online, and polling every 10 seconds gives about 1,400 requests per second.per visit: use daily users1,000,000daily users500 rpsx 50 calls / 100,000 s1,500 rpspeak, x 3about 14,000online, 20 min each1,400 rpspolling every 10 swhile open: use users online
Pick the path by what the request is tied to. Per-visit work divides by the seconds in a day; per-second work starts from how many people have the app open at once.

Which users: daily or concurrent

Two user counts get mixed up. Daily active users (DAU) is everyone who opens the app in a day: use it for things that happen per visit, like page loads. Concurrent users is how many have the app open at the same moment: use it for anything that happens per second while open, like polling or open WebSocket connections.

If you only know DAU, a rough way to get concurrent users is DAU times the average session length divided by the length of the day. A million daily users who each spend 20 minutes is about 1,000,000 x 20 / 1,440, or roughly 14,000 online on average, with a few times that at peak. Say this is an estimate; it hides a lot about time zones and habits.

Four cases that come up again and again

  • Polling. 100,000 tabs open, each polling every 5 seconds, is 20,000 requests per second, all day, even when nothing changes. Moving to a push channel (server-sent events or WebSockets) turns that into 100,000 open connections and messages only when something happens. Polling every 30 seconds instead is about 3,300 per second: sometimes good enough.
  • Autocomplete. Typing "wireless headphones" is 19 characters. With no debounce that is up to 19 requests per search. With a 250 ms debounce most people trigger 2 or 3. Across 10,000 searches per minute at peak, that is the difference between about 3,200 and about 500 requests per second.
  • Page views. 2 million page views a day, each making 6 API calls, is 12 million calls: about 120 per second on average and 300 to 400 at peak. Small, unless each call is slow.
  • Bandwidth. 1,500 requests per second at 40 KB each is 60 MB per second leaving your servers or CDN, about 5 TB a day if sustained. Cutting the payload in half cuts that in half.

The frontend has many levers on these numbers: debounce and throttle input, cache responses in memory and in the HTTP cache, deduplicate identical requests from different components, batch several small calls into one, back off polling when the tab is hidden (the visibilitychange event), and stop retry storms with exponential backoff and jitter.

The peak you create yourself

Some peaks are caused by the client. If 200,000 open tabs all reconnect the moment a server restarts, the server sees 200,000 connection attempts in a second or two. If every client polls on a fixed timer that started at the same deploy, requests arrive in waves. This is the thundering herd, and the frontend fix is jitter: add a random delay (say 0 to 10 seconds) so the same load is spread out.

Check your understanding · Exercise 7

A chat app has 300,000 concurrent users. The first design polls for new messages every 3 seconds. What is the polling load, and what does it suggest? Pick one.

A worked example: the photo feed, end to end

Numbers are most useful together. Here is one design question taken through all four budgets, the way you might do it in five minutes on a whiteboard. The prompt: "Design the home feed of a photo-sharing app, mobile web first."

Step 1: state the assumptions

  • Users: 5 million daily, 500,000 online at peak ("let's assume").
  • Device: a mid-range Android phone on the Lighthouse mobile profile (150 ms, 1.6 Mbps, 4x CPU).
  • A session scrolls about 200 posts. Each post: one photo shown 390 CSS pixels wide, square, at DPR 3.
  • A post card is about 40 elements; the feed API returns 10 posts per page at about 1.5 KB each.

The photo feed, one budget at a timeStep 1 of 6: Assumptions

A step-through of the photo feed estimate. The assumptions sit on the left. Budget rows appear on the right one by one: network about 2 seconds, CPU about 0.5 seconds, photo memory 1.1 GB, DOM 8,000 elements, server 25,000 requests per second. The last step shows photo memory at about 12 MB and the DOM at about 200 elements after virtualization.5M daily, 500k onlinemid phone, 150 ms, 1.6 Mbps200 posts per sessionphoto 390 px wide, DPR 340 elements per cardassume, compute, decide
Say them out loud first: 5 million daily users, 500,000 online at peak, a mid-range phone on the Lighthouse mobile profile, 200 posts per session, square photos 390 CSS px wide at DPR 3, 40 elements per card.

Assumptions: Say them out loud first: 5 million daily users, 500,000 online at peak, a mid-range phone on the Lighthouse mobile profile, 200 posts per session, square photos 390 CSS px wide at DPR 3, 40 elements per card.

Step 2: one line per budget

Each row is one multiplication and one comparison. The decision column is what the interviewer actually wants to hear.
BudgetMathResultDecision
Network, first load4 RTT x 150 ms, plus (150 KB JS + 120 KB first photo) x 8 / 1.6 MbpsAbout 0.6 + 0.75 + 0.6 s, so about 2 sInline critical CSS, preload the first photo, keep JS for the first route near 150 KB
CPU150 KB x 3.5 = 525 KB parsed, at 1 s per MBAbout 0.5 sAcceptable; lazy-load the comment composer and the share sheet
Memory, photos1170 x 1170 x 4 = 5.5 MB each, x 200 postsAbout 1.1 GBUnacceptable: virtualize and serve 2x, not 3x
Memory, DOM200 posts x 40 elements8,000 elementsOver 1,400: virtualize
Server500,000 online, 1 page of posts per 20 s of scrollingAbout 25,000 rps at peakCache feed pages at the edge per user cohort; prefetch one page ahead, not five

The photo memory line is the surprise, and it is the reason to estimate at all. A 390 CSS pixel wide image at DPR 3 is 1,170 real pixels wide. Keeping 200 of them decoded is over a gigabyte, which no phone will allow. With virtualization keeping about 5 posts mounted and photos served at 2x (780 pixels, about 2.4 MB decoded), the feed holds roughly 12 MB of photo bitmaps however long the session runs.

Step 3: try it yourself

The calculator below does the same math with sliders. Change one input at a time and watch which budget moves. Try making the JavaScript bigger on the mobile profile, then switch to desktop; try a DPR 3 grid with and without virtualization; try polling every 2 seconds with a million users.

Estimate:
Network:
Device:
300 KB of JavaScript and 200 KB of images on Lighthouse mobile with a mid-range phone: about 0.60 s to connect, 2.5 s to download, and 1.1 s to parse and run about 1.1 MB of JavaScript, 4.2 s in all.LCP 2.5 sconnect0.6 sdownload2.5 sparse and run1.1 s0 s1 s2 s3 s4 s5 sabout 4.2 s before the first screen is usable
Connect is 4 round trips; download is the compressed bytes over the bandwidth; parse and run uses about 3.5 times the JavaScript bytes at a per-MB rule of thumb for the device. Images cost download time here, not main-thread time.

Notice how often one input dominates. On mobile, round trips and CPU matter more than people expect; on desktop, almost nothing about a 150 KB bundle matters. For memory, DPR and virtualization outweigh everything else; for servers, the polling interval is the whole story. Finding the dominant term is the skill; the arithmetic is easy.

Check your understanding · Exercise 8

In the feed example, the team proposes switching photos from JPEG to AVIF to fix the tab crashes on long sessions. What does the estimate say? Pick one.

Where estimates go wrong

An estimate is only as good as its inputs and units. These are the mistakes that turn a sensible-sounding number into a wrong decision, roughly in order of how often they happen.

A matrix of six estimation mix-ups against four budgets: network, CPU, memory, and server. Bits versus bytes is off by 8 and corrupts network and server estimates. Compressed versus parsed JavaScript is off by about 3.5 and corrupts network and CPU. File versus decoded image size is off by 20 or more and corrupts memory. CSS versus device pixels at DPR 3 is off by 9 and corrupts network and memory. Average versus peak is off by 2 to 3 and corrupts server load. 1,000 versus 1,024 is off by 2 percent and corrupts nothing that matters.off bybits vs bytesx 8compressed vs parsed JSx 3.5file vs decoded imagex 20+CSS vs device pxx 9average vs peakx 2 to 31,000 vs 1,024x 1.02netCPUmemserverxxxxxxxx
Every trap but the last is a factor of 2 to 20, enough to flip a decision. The last one is the only mix-up not worth worrying about.

Unit and size mix-ups

  • Bits for bytes. A factor of 8. "10 MB on a 10 Mbps link takes a second" is wrong; it takes 8.
  • Compressed for parsed. A factor of 3 to 4 for JavaScript. Download time uses the compressed size; CPU time uses the parsed size.
  • File size for memory. A factor of 20 or more for photos. Memory uses width times height times 4.
  • CSS pixels for device pixels. A factor of 4 at DPR 2, 9 at DPR 3.
  • 1,000 or 1,024. Doesn't matter. A 2 percent difference never changes a decision; pick whichever makes the math easier.

Assuming your own device and network

Developers work on fast laptops on office Wi-Fi. Their users often do not. An estimate made on a laptop's numbers will be off by five to ten times on a mid-range phone, which is exactly the range where decisions flip. Anchor every user-facing estimate to a named profile, such as the Lighthouse mobile profile, and say so.

The same goes for medians. Core Web Vitals are judged at the 75th percentile of real page loads, not the median, so a page that is fine for the typical user can still fail. When you quote a target, quote the percentile with it.

Averages, peaks, and the long session

  • Average traffic for capacity. Servers fail at the peak. Multiply the daily average by a peak factor, and say which factor you picked.
  • Daily users for concurrent load. DAU is not how many are online at once. For polling and connections, use concurrent users.
  • The first screen for a long-lived tab. A feed is fine at 20 posts and broken at 2,000. Ask how long a session lasts, and estimate at the end of it, not the start.
  • Forgetting retries. A failing endpoint with three retries per client is four times the load at the worst moment. Backoff and jitter are part of the estimate.

False precision and wasted time

The opposite mistake is to treat estimation as a math exam. Quoting "287.4 KB" or spending four minutes on long division signals that you missed the point. One or two significant figures, a sentence or two, and a decision. If the estimate lands near the limit, say "that's close, I'd measure it" rather than refining the arithmetic.

Finally, an estimate does not replace measurement once the product exists. Real-user monitoring (RUM) data from the field, Lighthouse in CI, and heap snapshots in DevTools are the truth. Estimation is for the moment before you have them: design reviews, interviews, and deciding what to build first.

Check your understanding · Exercise 9

A candidate says: "Our 2 MB hero video on a 20 Mbps connection loads in a tenth of a second." What went wrong? Pick one.

Using numbers in a system design interview

Estimation is a tool for making decisions, not a separate step of the interview. The RADIO framework lesson walks through the stages of a frontend design round; numbers belong in several of them, briefly, at the moment a choice depends on them. The interview signals lesson explains why: interviewers look for reasoned trade-offs, and a number is the quickest way to show one.

When to reach for a number

Decision tree. First: does a choice depend on a size? If no, skip the math. If yes, assume and compute, in about 20 seconds. Then: is the result ten times over or under the limit? If yes, decide and move on. If it is close, say so and propose to measure.a choice hinges on size?noskip the mathyesassume, compute10x from the limit?yesdecide, move onclosesay close, measure
Two questions decide whether a number is worth the interview time and how far to take it. Most estimates should end in the bottom-left box within half a minute.
  • While gathering requirements. Ask the questions that set the inputs: how many items, how long a session, which devices, how many users online. "Is this mostly mobile? Roughly how many messages in a busy channel?"
  • When choosing between options. Render all or virtualize, poll or push, load now or lazy-load, client-side search or server-side. One estimate per decision.
  • When setting a budget. "First route under 150 KB compressed; feed keeps at most about 1,000 elements mounted."
  • When the interviewer pushes on scale. "What if it's 10 times more users?" is an invitation to redo one line of math out loud.

Don't estimate things that don't drive a decision. A settings page with 12 toggles needs no DOM math. A backend storage estimate rarely changes anything in a frontend round. If you can't say what the number will change, skip it.

The four moves

  1. Assume, out loud. "Say a heavy user scrolls 500 posts in a session."
  2. Compute with round numbers. "At about 40 elements each that's 20,000 elements, and 500 photos."
  3. Decide. "That's well past what a mid-range phone handles smoothly, so I'll virtualize and unmount photos off screen."
  4. Invite correction. "If sessions are much shorter, we could start without virtualization."

The whole thing takes about 20 seconds. Write the key numbers in a corner of the whiteboard so you and the interviewer can refer back to them; it also makes the trade-off visible when you later say "remember the 20,000 elements".

How deep to go, by level

LevelWhat a good answer includes
JuniorKnows the big rows of the latency ladder; gets bits and bytes and DPR right; can say why a long list or a big bundle is a problem.
Mid-levelEstimates unprompted at key decisions; quotes the Lighthouse profile and DOM-size guidance; separates transfer from parsed size and file size from decoded size.
SeniorPicks the dominant term fast; ties estimates to budgets and percentiles; covers server load from client behaviour (polling, retries, herds); says what to measure once built and how.

Think first

The interviewer says: "Your estimate says 25,000 requests per second for the feed at peak. Is that a lot?" How do you answer without knowing their infrastructure?

Summary

  • Estimation is quick, rough math said out loud to choose between options: assume, compute with round numbers, decide, invite correction.
  • Frontend has four budgets: network (KB and round trips), CPU (ms on a mid-range phone), memory (MB in a long-lived tab), and server load (requests per second).
  • Most estimates are count times size per item, compared with a limit. If you're ten times over or under, the decision is made.
  • Quote a named profile: Lighthouse mobile is 150 ms round trips, 1.6 Mbps down, and a 4x CPU slowdown. The first request on a new HTTPS connection costs about four round trips.
  • JavaScript has two costs: compressed bytes to download and about 3 to 4 times as many bytes to parse and run, roughly 1 s per unzipped MB on a mid-range phone (a rule of thumb, right within a factor of two).
  • Decoded images cost width times height times 4 bytes, whatever the format; DPR squares it.
  • About 1,400 DOM elements is the long-standing Lighthouse guideline; virtualized lists keep the count flat at a few hundred.
  • Requests per second is users times requests per user divided by the window; use concurrent users for polling and add a peak factor for capacity.

In the interview

Questions interviewers ask about this. Answer each out loud first, then compare.

Question 1

Your photo grid shows thumbnails 120 CSS pixels wide on a phone with a DPR of 3. Roughly how much memory do 300 decoded thumbnails use, and what do you do about it?

Question 2

A product manager wants a 400 KB (compressed) charting library on the dashboard's first load. Argue it with numbers.

Question 3

Estimate the load if 200,000 people keep a dashboard open that polls every 10 seconds, and what you would change.

Question 4

How long does the HTML of a page take to start arriving for a first-time visitor on a slow mobile connection? Walk through it.

Question 5

When would you not bother to estimate in a frontend design interview?

Practice it

These design problems lean on back-of-the-envelope numbers for frontend.

References

Read next