How would you set and enforce a change detection performance budget for a large Angular application, given that Angular's profiling tools only work in development builds?
answer
- frame budget per interaction
- relative numbers from development builds
- saved recordings as evidence
- field data for the real user
- own the key interactions
basics
~20 sDefine budgets per key interaction (cycles per event, share of a frame spent in change detection), measure them relatively with the Profiler and the Chrome Angular track in development builds, and confirm against production traces and field data.
solid answer
~40 sA useful budget is per **interaction**, not per app: for example, typing in search causes one change detection cycle per keystroke, and that cycle fits comfortably within a 60 fps frame on a reference device. Because Angular DevTools and `enableProfiling()` only work in development builds, their numbers are **relative**: use them to check cycle counts, which components are checked, and trends against a saved baseline, not as user-facing milliseconds. Keep baseline recordings for the key interactions, require a before-and-after recording for changes that touch them, and treat a new unexpectedly checked subtree or an extra cycle as a regression. Anchor the absolute side in production-like traces and field metrics owned by the web-performance practice. There is no single right threshold; the tradeoff is enforcement cost against catching regressions early.
go deeper
Recall that Angular's profiling tools only work in development builds, so their timings are higher than production.
Explain which measures stay meaningful in a development build, such as cycle counts and which components were checked.
Set up baseline recordings for key interactions and review before-and-after profiles on changes that touch them.
Own the policy: which interactions are budgeted, structural versus timing limits, who owns each flow, and how production data backs it up.
## Why this is a judgement call A **performance budget** is an agreed limit that a change must not exceed. Build-size budgets are easy to enforce because the build measures them. A **change detection budget** is harder: the tools that see Angular's checking work, the Angular DevTools **Profiler** and the Chrome integration switched on by `enableProfiling()`, only run in **development builds**, and development builds do extra work. There is no single correct policy, only tradeoffs between precision, enforcement cost and developer friction. ## What to budget Budget the **interactions users feel**, not the whole application. A small list is enough: - typing in search or filter inputs; - opening a large table or dashboard; - toggling a row, a tab or a panel; - route transitions into heavy views. For each, pick measures the development tools can observe reliably: | Measure | Why it survives development-mode inflation | |---|---| | Change detection cycles per interaction | A count, not a duration | | Components checked per interaction | Structural, independent of machine speed | | Synchronization passes per cycle | More than one means state changed mid-check | | Relative cycle time versus a saved baseline | Same build mode and machine on both sides | | Share of the frame spent in change detection | Only as a trend, with the inflation acknowledged | Absolute user-facing latency belongs to **production-like traces** and **field metrics** such as interaction responsiveness, owned by the broader web-performance practice. The Angular-specific budget explains those numbers; it does not replace them. ## How to enforce it without a CI gate Because there is no production-mode Angular profiler, enforcement is a **process**, supported by artefacts: 1. **Baselines**: save Profiler recordings (JSON) of each key interaction on a reference machine and data set. 2. **Change rule**: a pull request that touches a budgeted flow attaches before-and-after recordings, or a Chrome trace with the Angular track. 3. **Regression signals** reviewers look for: an extra cycle per event, a subtree newly coloured in the Profiler's change detection view, a second synchronization pass, a large relative increase in the dominant component's time. 4. **Periodic sweep**: re-record the baselines each release, because data volume and features drift. 5. **Production confirmation**: before closing a performance bug, confirm with a production-like build and, where available, field data. ## Tradeoffs to argue - **Strictness versus friction**: requiring recordings on every change is thorough and slow; requiring them only for budgeted flows catches most regressions at a fraction of the cost. - **Structural versus timing budgets**: counts of cycles and checked components are stable and reviewable; timings are noisy but closer to what users feel. A mix works best. - **Central versus team ownership**: one performance owner keeps baselines consistent; team ownership scales but drifts. Many organisations assign each key interaction to the team that owns the feature. - **Architecture as the cheaper budget**: defaults such as `OnPush` (the default since v22), signal-derived state and isolated heavy components make many budget checks pass by construction, which is cheaper than policing each change. ## How budget policies fail - **Too many budgets**: twenty flows with recordings nobody re-runs become shelfware. Fewer, owned flows survive. - **Timing-only budgets**: development-build milliseconds swing with machine load, so reviewers start ignoring them; structural measures keep the signal. - **No link to users**: a budget met in development recordings while field responsiveness worsens is measuring the wrong thing, and needs a production trace to find out why. - **Budgets without architecture**: policing every template by hand is expensive; pushing heavy views into isolated components with signal-derived inputs reduces how often the budget is at risk at all. ## A reasonable starting policy - Five to ten budgeted interactions, each with an owner and a saved baseline recording. - One change detection cycle per discrete user event, one synchronization pass per cycle, and no checks of heavy subtrees unrelated to the interaction. - Recordings required on pull requests that touch those flows. - Production-like confirmation for anything reported by users. State clearly in an interview that the numbers themselves are negotiable. What matters is picking measures the tools can observe honestly and pairing them with a real-user signal.
- Why budget change detection cycles per event rather than milliseconds per cycle?Cycle counts do not depend on machine speed and are largely the same in development and production builds, so they are reviewable from a development recording. Milliseconds from a development build are inflated and vary by hardware; use them as trends against a baseline and leave absolute latency to production-like measurements.
- A team argues budgets are unnecessary because components are OnPush by default since v22. How do you respond?OnPush reduces checking only when inputs keep stable references and signals are scoped well. A single template method returning a new array, or a broadly read signal, brings back full checks. The default makes budgets easier to meet; it does not verify that they are met.
saying these in an interview costs you the question
- Development-build Profiler times can be quoted as user-facing latency
- OnPush by default makes change detection budgets unnecessary
- A single app-wide millisecond threshold fits every interaction
- Angular's profiling tools can gate production builds in CI directly
- Field metrics make Angular-specific profiling redundant