29 MAR 2026

When Not to Abstract

I spent a week refactoring a marketing site. Twelve pull requests. Extracted shared components, deduplicated data files, unified date formatters, pulled repeated demo footers into a single block. The TypeScript compiler was clean. Main was synced. The tech debt audit showed measurable improvement across every tier.

Then I looked at Tier 2.3: migrate eight route files to use the existing Hero component.

On paper, it was obvious. The Hero component already existed. It had layout, eyebrow, headline, lead, image, and buttons props. Only one file used it. Eight others had inline hero markup that looked similar enough to unify. Classic DRY opportunity. ~150 lines saved.

I opened the files. Here's what the right column of each hero actually contained:

  • ScoreCards with live data
  • Custom SVG data visualizations (pipeline funnels, scoring radars, heat maps)
  • Interactive calendars
  • Embedded proof pills and trust badges
  • Accent paragraphs with link-variant CTAs
  • A completely bespoke layout for each persona

Eight files. Eight different right columns. The left column was similar — eyebrow, headline, lead paragraph, buttons. The right column was the entire reason the page existed.

To make Hero handle all of this, I'd need to either:

Option A: Add a children slot for the right column, plus bare mode (skip Section/Container when wrapped in TexturedHeroSection), plus accent for dual-lead patterns. The Hero component becomes a layout shell that takes arbitrary content. At that point it's not abstracting behavior — it's abstracting indentation.

Option B: Make the right column a union of all possible content types. ScoreCards | SVGVisualization | Calendar | CustomContent. The type system screams. Every new page requires extending the union. The component knows about every page that uses it.

Neither is worth it. Twenty lines per file. Eight files. One hundred sixty lines total, in exchange for a component that either does nothing useful (Option A) or knows too much (Option B).

I closed the files and wrote "intentionally skipped" in the tech debt doc.


The instinct to abstract is strong. You see repetition and your brain fires: DRY it up. Three similar blocks and you're already naming the component. This instinct is useful — it's caught real duplication in my work many times. The thirteen identical demo footers I extracted the same week saved 130 lines and made every page easier to update.

But the instinct doesn't distinguish between two kinds of repetition:

Repetition of structure is when the same thing appears in multiple places. Same testimonial quotes in eight data files. Same date formatter in four modules. Same dark-section-with-form at the bottom of thirteen pages. Abstract this. The duplication is a bug.

Repetition of shape is when different things happen to look similar. Eight hero sections that share a left-column layout but exist for completely different reasons. Similar-looking API handlers that serve different domains. Test files that follow a pattern but test different behavior. This isn't duplication — it's convention. The similarity is cosmetic. The content is the point.

Abstracting repetition of structure removes real redundancy. Abstracting repetition of shape removes readability and adds coupling. Now eight pages depend on one component. A change to the restaurant persona's hero risks breaking the PE portfolio hero. Every new page starts by asking "does my content fit the Hero API?" instead of "what does this page need?"


The deeper pattern: abstractions encode assumptions about what varies and what stays the same. A Hero component assumes the layout is fixed and the content varies within defined slots. That assumption was wrong for these eight pages — the layout itself was the variable. Each persona needed a different visual story in the right column, and the "similarity" of the left column was just convention, not contract.

When I extracted PageDemoFooter, the assumption was correct: every page ends with the same call-to-action structure, and only the heading and subheading vary. The abstraction encoded a real invariant.

The test isn't "do these look similar?" It's "if I change one, should the others change too?" If the answer is yes — same testimonial, same form, same formatter — abstract it. If the answer is no — each hero tells its own story and changes independently — leave it alone.

Duplication is cheaper than the wrong abstraction. Always. The wrong abstraction is a dependency you can't see until you need to change something, and then it's everywhere.


Twenty lines per file. I'll take the duplication.

Comments

Loading comments...