Detailed explanation
CSS performance involves both delivering styles efficiently and reducing the browser’s work when rendering or updating a page. After parsing HTML and CSS, the browser resolves styles, calculates layout, paints pixels, and composites layers. Different changes trigger different stages: changing an element’s width can require layout and paint, while transform and opacity animations can often run through compositing. In interviews, explain which cost each practice reduces rather than simply listing optimisation tools.
Keep selectors understandable and avoid unnecessarily long descendant chains. For example, replace body div ul li a span with .link-text when that class accurately identifies the target. Browsers generally match selectors from right to left, but modern engines optimise matching heavily, so deep selectors are not automatically your biggest bottleneck. Large DOM trees and frequent style invalidation can matter more. Profile representative interactions before rewriting selectors, and prefer simpler rules for maintainability as well as potentially cheaper matching.
Avoid layout thrashing when JavaScript interacts with styles. Writing a layout-affecting style and immediately reading geometry, such as offsetWidth or getBoundingClientRect(), can force synchronous layout if rendering information is stale. Repeating that pattern inside a loop is especially costly. Group reads together, then apply writes; requestAnimationFrame can help schedule visual updates, but does not automatically prevent forced layout. For animations, prefer transform and opacity where appropriate. Apply will-change only to elements that are about to animate, then remove it afterwards; unnecessary layer promotion can consume memory without improving responsiveness.
Reduce download and parsing costs by minifying CSS, enabling Brotli or gzip compression, and removing unused rules with tools such as PurgeCSS. Safelist classes constructed dynamically by JavaScript or template code, otherwise production styling can break. Split styles by route or component and defer genuinely non-critical styles, while keeping above-the-fold styling available to avoid flashes of unstyled content or layout shifts. Stylesheets do not support native loading="lazy", so deferred delivery needs an intentional loading strategy. Validate changes using browser performance recordings, network inspection, and CSS coverage across relevant pages and states.
Key points to remember
- Prefer focused class selectors over unnecessarily deep descendant chains; measure before optimising.
- Batch geometry reads and style writes to avoid repeated forced synchronous layouts.
- Prefer transform and opacity for suitable animations, and use will-change temporarily.
- Minify, compress, and split CSS while keeping initial-render styles available.
- Remove unused CSS carefully, preserving dynamically generated classes and conditional states.
Common mistakes candidates make
- Assuming selector complexity is always the main bottleneck without profiling rendering work.
- Alternating style writes and geometry reads inside loops, even within requestAnimationFrame.
- Deferring essential styles, enabling will-change everywhere, or purging classes without checking runtime usage.
Likely follow-up questions
What is layout thrashing?
Layout thrashing happens when code repeatedly invalidates layout and then reads geometry that requires an immediate layout calculation. Read all required measurements first, then batch updates; if measurements must reflect those updates, reorganise the work into deliberate phases.
Why should will-change be used sparingly?
It hints that a property will change, allowing the browser to prepare optimisations such as a compositing layer. Those preparations can consume memory and other resources, so use it for demonstrated performance needs rather than on every element.
How do you safely remove unused CSS?
Configure the removal tool to scan all relevant templates and scripts, and safelist classes it cannot detect statically. Test routes, responsive layouts, and interactive states because coverage from one page visit does not prove a rule is unused everywhere.
Interview tip: Structure your answer around delivery, rendering, and measurement, then illustrate one issue with a concrete selector or read/write example.
