Performance Budget System Design
System design for performance budgets — metrics, CI gates, RUM feedback loops, ownership, and enforcement.
- system-design
- interview
- architecture
- performance
Scope the problem
A performance budget system is how an org decides limits, measures them, and blocks regressions — not a one-time Lighthouse run.
In scope: which metrics, lab vs field, CI integration, dashboards, ownership, exception process.
Goals
- Prevent “death by a thousand KB”
- Make perf regressions visible before prod
- Align product tradeoffs with user impact (CWV)
Metric layers
| Layer | Examples | When |
|---|---|---|
| Resource | JS KB gzip per route, image weight | CI, PR |
| Lab CWV | Lighthouse LCP/INP/CLS | CI sample URLs |
| Field RUM | p75 LCP/INP/CLS by route | production |
| Custom | TTI login, time-to-ATC | product-critical |
Budgets without field data optimize lab fiction.
Example budgets
| Surface | Budget |
|---|---|
| Marketing landing JS | ≤ 90KB gzip initial |
| App shell JS | ≤ 150KB gzip |
| PDP LCP (4G lab) | ≤ 2.5s |
| Checkout INP p75 field | ≤ 200ms |
| CLS p75 | ≤ 0.1 |
Different routes → different budgets. Global single number is too blunt.
System architecture
PR build
→ bundle analyzer stats
→ size compare vs base branch
→ Lighthouse CI on key URLs
→ pass / warn / fail
Prod
→ RUM SDK vitals
→ daily report by route owner
→ alert on regression
CI enforcement design
// conceptual check
if (routeSizes["checkout"] > budgets.checkout.jsGzip) {
fail("Checkout JS exceeded budget");
}
Modes:
- Hard fail on critical routes
- Warn on experimental
- Temporary waivers with expiry owner
Waivers without expiry = budgets dead.
Bundle analysis
- Track per-route entry chunks
- Fail on unexpected new large dependency
- Duplicate package detection (
lodashvslodash-es)
Lab vs field differences
| Lab | Field |
|---|---|
| Controlled device | real devices |
| Cached warm often | cold cache users |
| No third-party variance | tags, consent, ads |
Use lab for PR signal; field for truth and OKRs.
Ownership model
Route / product surface → team → budget → dashboard
Platform team owns tooling; product teams own their numbers. Perf is a feature requirement, not a platform-only hobby.
Feedback loops
- PR blocks egregious regressions
- Weekly review of field p75
- Release annotation on RUM charts
- Postmortems when CWV hurts SEO/revenue
Tooling components
| Piece | Role |
|---|---|
| webpack/vite stats | sizes |
| Lighthouse CI | lab |
| web-vitals + backend | RUM |
| bundlesize / size-limit | PR gates |
| Source maps + traces | debug |
Handling third parties
- Budget third-party bytes separately
- Tag managers load after interaction when possible
- Contract with marketing on script weight
Tradeoffs
- Strict CI vs ship speed — start with top 5 routes
- Synthetic only misses real pain
- Optimizing averages vs p75/p95 users
- Micro-optimizations vs architectural splits
Interview close
Define multi-layer budgets → per-route ownership → CI size + Lighthouse gates with waiver policy → RUM verification → third-party accounting. Tie to business metrics (conversion, SEO).
Example PR comment bot
❌ checkout.js 184KB gzip (budget 150KB) (+22KB vs main)
⚠️ LCP lab /product/sku 2.8s (budget 2.5s)
✓ CLS 0.02
Engineers should see which dependency grew (why-did-you-bundle / analyzer link).
Organizational policy
- Budgets live in repo (
perf-budgets.json) reviewed like code - Changing a budget is a conscious PR, not a silent config tweak on CI day
- Pair with design system: new heavy components need platform review
Mobile-first emphasis
Field INP and LCP on mid-tier Android often dominate pain. Don’t celebrate desktop lab greens while p75 mobile burns. Split dashboards by device class.
Related on this site
- Core Web Vitals
- Error Monitoring and RUM Design
- Code Splitting Strategies
- Performance Interview Talking Points