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.
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.
| Budget | Unit | Who pays | Typical limit to compare against |
|---|---|---|---|
| Network | KB transferred, round trips | The user, waiting | LCP within 2.5 s on Lighthouse's slow 4G profile |
| CPU | ms of main-thread work | The user, on their phone | 16.7 ms per frame, 50 ms per task, 200 ms per interaction |
| Memory | MB in the tab | The user's device | A few hundred MB before a cheap phone starts killing tabs (rule of thumb) |
| Server | Requests per second, MB per second | Your company | Whatever 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.
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)
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.
| What | Typical time | Where the number comes from |
|---|---|---|
Read a value from a JavaScript Map | Well under 1 microsecond | Rule of thumb |
| Read a file from the HTTP disk cache | A few ms, more on slow phones | Rule of thumb; check the Timing tab in DevTools |
| One frame at 60 Hz | 16.7 ms | 1,000 ms divided by 60 |
| A long task | Over 50 ms | W3C Long Tasks API definition |
| Interaction to Next Paint (INP), good | 200 ms or less | web.dev Core Web Vitals thresholds |
| Round trip to a nearby CDN edge | 10 to 30 ms on a good connection | Rule of thumb |
| Round trip New York to London | About 56 ms at the speed of light in fibre, 70 to 80 ms in practice | Physics, plus routing overhead |
| Round trip on Lighthouse's mobile profile | 150 ms | Lighthouse throttling docs |
| Largest Contentful Paint (LCP), good | 2.5 s or less | web.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.
| Profile | Round trip | Download | Upload | CPU |
|---|---|---|---|---|
| Lighthouse mobile ("Slow 4G") | 150 ms | 1.6 Mbps | 750 Kbps | 4x slowdown |
| Chrome DevTools "Fast 4G" preset | 60 ms | 9 Mbps | 1.5 Mbps | Set separately |
| Chrome DevTools "3G" preset | 400 ms | 500 Kbps | 500 Kbps | Set separately |
| Lighthouse desktop | 40 ms | 10 Mbps | Not modelled | No slowdown |
| Typical home broadband (rule of thumb) | 10 to 30 ms | 50 to 500 Mbps | 10 to 100 Mbps | Fast 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.
preconnect starts them early for an origin you know you will need.- DNS lookup: turn the host name into an IP address. One round trip to a resolver, often cached.
- TCP handshake: open the connection. One round trip.
- TLS 1.3 handshake: agree on encryption keys. One round trip (TLS 1.2 needed two).
- 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.
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":
| Median page, July 2025 | Mobile | Desktop |
|---|---|---|
| Total home page weight | 2,559 KB | 2,862 KB |
| Images (home page) | 911 KB | 1,058 KB |
| JavaScript (home page) | 632 KB | 697 KB |
| Requests (all pages) | 72 | 77 |
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.
// 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
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.DateTimeFormatis 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.
| Image | Pixels | JPEG | WebP | AVIF |
|---|---|---|---|---|
| Avatar or thumbnail | 200 x 200 | 10 to 20 KB | 8 to 15 KB | 5 to 10 KB |
| Feed photo on a phone | 1080 x 1080 | 100 to 250 KB | 70 to 180 KB | 50 to 120 KB |
| Desktop hero | 1920 x 1080 | 200 to 400 KB | 150 to 300 KB | 100 to 200 KB |
| Original from a phone camera | 4000 x 3000 (12 MP) | 2 to 5 MB | Not served as is | Not 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.
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)
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
| Item as JSON | Size per item | Page of 20 | 10,000 items |
|---|---|---|---|
| Contact (name, email, avatar URL) | About 200 bytes | 4 KB | 2 MB |
| Feed post (text, author, counts, media URLs) | 1 to 2 KB | 20 to 40 KB | 10 to 20 MB |
| Chat message | 300 to 500 bytes | 6 to 10 KB | 3 to 5 MB |
| Table row with 15 columns | 500 bytes to 1 KB | 10 to 20 KB | 5 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.
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:
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
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.
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
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
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
| Budget | Math | Result | Decision |
|---|---|---|---|
| Network, first load | 4 RTT x 150 ms, plus (150 KB JS + 120 KB first photo) x 8 / 1.6 Mbps | About 0.6 + 0.75 + 0.6 s, so about 2 s | Inline critical CSS, preload the first photo, keep JS for the first route near 150 KB |
| CPU | 150 KB x 3.5 = 525 KB parsed, at 1 s per MB | About 0.5 s | Acceptable; lazy-load the comment composer and the share sheet |
| Memory, photos | 1170 x 1170 x 4 = 5.5 MB each, x 200 posts | About 1.1 GB | Unacceptable: virtualize and serve 2x, not 3x |
| Memory, DOM | 200 posts x 40 elements | 8,000 elements | Over 1,400: virtualize |
| Server | 500,000 online, 1 page of posts per 20 s of scrolling | About 25,000 rps at peak | Cache 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.
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.
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
- 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
- Assume, out loud. "Say a heavy user scrolls 500 posts in a session."
- Compute with round numbers. "At about 40 elements each that's 20,000 elements, and 500 photos."
- Decide. "That's well past what a mid-range phone handles smoothly, so I'll virtualize and unmount photos off screen."
- 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
| Level | What a good answer includes |
|---|---|
| Junior | Knows 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-level | Estimates unprompted at key decisions; quotes the Lighthouse profile and DOM-size guidance; separates transfer from parsed size and file size from decoded size. |
| Senior | Picks 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?
Put it in context rather than guessing their capacity. 25,000 per second of cacheable, mostly identical feed pages is easy for a CDN; 25,000 per second of personalized queries that each hit a database is a real backend project.
Then say what the frontend can do about it: prefetch only one page ahead, cache pages in memory so going back doesn't refetch, and make the first page cacheable at the edge for logged-out users. Each of those changes one term in the formula.
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?
At DPR 3 each thumbnail is 360 by 360 device pixels, about 130,000 pixels at 4 bytes each, so roughly 0.5 MB. 300 of them is about 150 MB of bitmaps, enough to get a tab reloaded on a mid-range phone.
Virtualize the grid so only the visible rows and a small overscan stay mounted, serve thumbnails at the size shown with srcset and sizes (capping at 2x saves more than half), and lazy-load rows below the screen. The file format doesn't change the memory number.
Question 2
A product manager wants a 400 KB (compressed) charting library on the dashboard's first load. Argue it with numbers.
400 KB compressed is about 1.4 MB to parse. On the Lighthouse mobile profile that is about 2 seconds to download (3,200 kilobits at 1.6 Mbps) and, by the 1 second per MB rule of thumb, about 1.4 seconds of main-thread work on a mid-range phone, before the dashboard responds. Most users who never open a chart pay it too.
Offer options: load the library when a chart scrolls into view or its tab opens, render a static image or a skeleton first, or pick a lighter library. Then hold the first route to a budget, such as 150 KB compressed, checked in CI.
Question 3
Estimate the load if 200,000 people keep a dashboard open that polls every 10 seconds, and what you would change.
200,000 divided by 10 is 20,000 requests per second, all day, mostly returning nothing new. Cheap 304 Not Modified answers help the bytes, not the request count.
Options: stop polling when the tab is hidden, which can remove a large share of it; poll less often for data that changes slowly; or move to server-sent events so the server pushes only changes. Add jitter to reconnects so a deploy doesn't cause a thundering herd.
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.
Assume the Lighthouse mobile profile, 150 ms round trips. A new HTTPS connection needs DNS, TCP, and TLS 1.3, one round trip each, and then the request itself: four round trips, about 600 ms, before the first bytes arrive. HTTP/3 saves one round trip; a CDN edge close to the user shrinks every round trip.
Then the first 14 KB or so arrives in the first flight because of TCP's initial congestion window, which is why critical CSS and the top of the HTML should fit early. Any extra origin for fonts or scripts repeats the connection cost unless you preconnect.
Question 5
When would you not bother to estimate in a frontend design interview?
When the number can't change a decision: a settings form, a modal, a list known to have ten items, or backend storage that the frontend doesn't control. Estimating there spends time without buying a reason.
Also when you'd need precision you don't have. If an estimate lands within a factor of two of the limit, say it's close and you'd measure, rather than refining the arithmetic.
Practice it
These design problems lean on back-of-the-envelope numbers for frontend.
References
- HTTP Archive Web Almanac 2025: Page Weight Median page weight, image and JavaScript bytes, and request counts (July 2025 data).
- Lighthouse: Network throttling The 150 ms, 1.6 Mbps mobile profile and how simulated throttling works.
- Chrome for Developers: Avoid an excessive DOM size The older Lighthouse audit with the 800 and 1,400 element thresholds.
- Chrome for Developers: Optimize DOM size insight The Lighthouse 13 and DevTools insight that replaced it, based on style and layout time.
- web.dev: Web Vitals LCP, INP, and CLS thresholds and the 75th percentile rule.
- V8 blog: The cost of JavaScript in 2019 Download and execution are the main costs of JavaScript, and why phones pay more.
- RFC 6928: Increasing TCP's Initial Window The 10-segment initial congestion window behind the 14 KB rule.
- Google: WebP compression study WebP lossy files 25 to 34 percent smaller than comparable JPEGs.
- Alex Russell: The Performance Inequality Gap, 2024 Device and network baselines, and JavaScript budgets for real users.
Read next
- The RADIO frameworkA five-part checklist, Requirements, Architecture, Data model, Interface, and Optimizations, for running a frontend system design interview: design the right thing, cover the basics fast, and save time for deep dives.
- What interviewers look for, by levelThe six areas interviewers typically score in a frontend system design round, how the bar rises from junior to staff, and how to make the signals of your target level visible, minute by minute.
- Core Web VitalsThree numbers, measured on real users, that say whether a page loads fast (LCP), reacts fast (INP), and stays still (CLS): what each one measures, what makes it bad, and how to fix it.
- The critical rendering pathEverything the browser must fetch and compute before the first pixels: HTML, render-blocking CSS, parser-blocking scripts, then style, layout, paint, and composite, and how to count, measure, and shorten it.
- HTTP cachingThe browser's built-in cache, steered by headers on each response, which lets a repeat visit skip the network entirely or ask only "has this changed?": the cheapest speed-up on the web.
- Client-side renderingThe server sends an almost empty HTML page plus a JavaScript bundle, and the browser builds the whole page itself: easy to host and great behind a login, but slow to first content and weak for search.