skip to content

You find the same long string of utility classes repeated on a dozen elements across a codebase. What signals tell you it is time to extract a component, and what are your options for doing that extraction?

level: seniorimportance: should knowfreq 50%

answer

  1. duplication of knowledge, not of text
  2. would all twelve change together?
  3. does the thing already have a name?
  4. extract the markup, not necessarily the class
  5. a new shared class is a new global

basics

~20 s

Extract when the repetitions must change together — the repeated string represents one concept, not a coincidence. Prefer extracting the markup into a reusable template or component so the class string exists once; extracting a CSS class instead reintroduces the shared-rule blast radius.

solid answer

~60 s

Repetition alone is not the signal; **coupled change** is. Ask whether, if this styling changed, every one of those twelve places would have to change with it. If yes, they are one concept and duplication is a liability. If they merely happen to share values today — a sidebar and a modal that both use 1rem padding — extracting couples two things that should be free to diverge, which is the classic premature abstraction. Second signal: does the repeated thing have a name people already say out loud? "Card", "toolbar", "badge" mean it is a real unit; "the flex row with gap" does not. When you do extract, prefer extracting the **markup** — a template, partial or component that renders the class string once — because that removes duplication while keeping styling local and deletable. Extracting a CSS class instead is legitimate when there is no markup layer to hold the abstraction, but you have then re-created a global shared rule with unbounded blast radius, and you should name that trade rather than pretend it is free.

code

html · 10 lines
html
<!-- Repeated across many pages: the class string is the duplication -->
<div class="flex gap-2 p-4 border">Item A</div>
<div class="flex gap-2 p-4 border">Item B</div>

<!-- Route B: extracted as a shared CSS class — one name, global reach -->
<style>
  .card { display: flex; gap: 0.5rem; padding: 1rem; border: 1px solid #d0d0d0; }
</style>
<div class="card">Item A</div>
<div class="card">Item B</div>

go deeper

for a junior

Know that repeating the same classes is not automatically wrong, and that the usual fix is to reuse the markup — a template or component — rather than to invent a new shared CSS class.

for a middle

Explain the coupled-change test: extract when all occurrences would have to change together, because that is duplicated knowledge rather than duplicated text.

for a senior

Show that you weigh premature abstraction seriously and that you know extracting markup and extracting a CSS class have different long-term costs — the second creates a new global with unbounded reach.

for a principal

Own the policy question: what the team's default extraction route is, when a shared class is warranted, and how you keep components from being split at seams that force reshaping props later.

## Repetition is not the signal The instinct on seeing the same class string twelve times is that duplication is a defect. Often it is not. Two elements that share `display: flex; gap: .5rem; padding: 1rem` may be the same concept appearing twelve times, or twelve unrelated things that happen to sit at the same point in the design's value space. Only the first is duplication in the harmful sense. The discriminating question is **coupled change**: if this styling changed, would all twelve have to change together? That is the same test used for duplication anywhere in software — the rule that duplication of *knowledge* is the problem, not duplication of *text*. Twelve places that must move in lockstep are one concept typed twelve times, and every future change will have to find all twelve. Twelve places that coincidentally match are independent, and binding them together means the day one needs to change, someone either changes all twelve or forks the abstraction — both bad outcomes caused by the extraction. ## Concrete signals to extract - **Coupled change**, as above — the strongest signal. - **The thing has a name.** If designers and engineers already say "card" or "toolbar", the concept exists socially and deserves a boundary. If you have to invent a name to extract it, that is a warning. - **It carries behaviour or structure, not just declarations.** A repeated fragment that includes markup structure, ARIA attributes, or an icon plus label is a component regardless of styling. - **The repetitions have already drifted.** If eleven use 1rem and one uses 0.75rem with nobody knowing why, the concept exists but is decaying, and extraction fixes it. ## Signals to wait - **Fewer than three occurrences.** Two is a coincidence; three is a pattern. Extracting at two is where most premature abstractions are born. - **Different domains.** A marketing hero and an admin table row that look alike are not one thing. - **You cannot name it.** `FlexRowWithGapAndPadding` is a name that admits the abstraction is not real. - **Foreseeable divergence.** If one of the twelve is already scheduled to change, do not bind it to the other eleven this week. ## The two extraction routes **Route A — extract the markup.** Move the element into a reusable template, partial, or component, and the class string is written once and rendered many times. This is the route utility-first assumes. The styling stays local to one place, and deletion stays safe: delete the component, and its styling goes with it because no global rule was ever created. ```html <!-- before: repeated on twelve pages --> <div class="flex gap-2 p-4 border">…</div> <!-- after: one template renders it; the class string exists once --> <div class="flex gap-2 p-4 border"><slot/></div> ``` **Route B — extract a CSS class.** Write `.card { … }` in the stylesheet and put one class on each element. This is a legitimate choice, and sometimes the only one: hand-written HTML with no templating, content authored in a CMS, an email template, markup produced by a system you do not control. It is also the right call for things utilities express badly — a multi-line `grid-template-areas`, a keyframe animation, a complex pseudo-element treatment. Be explicit about what Route B costs: you have created a global name with no record of its users, so future edits carry unbounded blast radius and the rule becomes hard to delete. That is not a reason never to do it; it is a reason to do it consciously, for concepts stable enough to be worth a shared name. ## Choosing between them The deciding question is whether a markup abstraction layer exists. If templates or components are already how the codebase is built, Route A is almost always better: it removes the duplication without creating a new global. If markup is hand-written across many files, Route A is unavailable and Route B is doing real work — which is precisely why utility-first is a poor fit for codebases without a component layer. A third option deserves mention: **do nothing**. Twelve occurrences of a coincidental pattern with no coupled change is not a problem, and leaving it costs a little repetition while preserving the freedom to let each instance evolve. Choosing not to abstract is a legitimate engineering decision, not laziness. ## What interviewers listen for They want to hear that you do not extract on repetition count alone, that you can name the premature-abstraction failure mode, and that you know extracting into markup and extracting into CSS have different consequences. A candidate who answers "three strikes and refactor" without the coupled-change test is applying a rule of thumb they have not thought about.

  • What is the concrete harm of extracting a shared class from twelve occurrences that only coincidentally match?
    You have declared that twelve independent things must look alike. The first time one needs to differ, someone either changes all twelve — a regression waiting to happen — or forks the abstraction into variants. Either way the extraction created work rather than saving it, which is the classic premature-abstraction outcome.
  • When is extracting a real CSS class the better route even in a componentized codebase?
    When the styling is not utility-shaped: a multi-line `grid-template-areas`, keyframes, complex pseudo-element treatments, print rules, or overrides for third-party markup you cannot edit. Utilities handle repetitive composition; the language's more structural features are better written as ordinary rules.
  • How do you decide the boundary of the extracted component itself?
    By what changes together and what is reused independently. If a header, body and footer always ship as a unit, they are one component; if the footer appears on its own elsewhere, it is its own. The wrong boundary shows up as props that only exist to reshape the inside — a sign you split at the wrong seam.

saying these in an interview costs you the question

  • Extracts on repetition count alone, ignoring coupled change
  • Treats any duplication as automatically a defect
  • Assumes extracting a shared class is always the right route
  • Names the abstraction after its declarations, not its concept
  • Never considers leaving the duplication in place

context