News Froggy
newsfroggy
HomeTechReviewProgrammingGamesHow ToAboutContacts
newsfroggy

Your daily source for the latest technology news, startup insights, and innovation trends.

More

  • About Us
  • Contact
  • Privacy Policy
  • Terms of Service

Categories

  • Tech
  • Review
  • Programming
  • Games
  • How To

© 2026 News Froggy. All rights reserved.

TwitterFacebook
Programming

Premature Abstraction: Don't Build the Reusable Component Yet — Key

As software developers, we're constantly encouraged to write clean, maintainable code, and a cornerstone of this philosophy is reusability. The conventional wisdom, especially in frontend development, often dictates

PublishedNovember 6, 2026
Reading Time8 min

As software developers, we're constantly encouraged to write clean, maintainable code, and a cornerstone of this philosophy is reusability. The conventional wisdom, especially in frontend development, often dictates that if you see similar UI patterns or workflows, you should immediately abstract them into a shared component. This promises the satisfaction of deleting redundant code and fostering consistency. However, this advice, while generally sound, can sometimes lead us down a costly path if applied prematurely. There are situations where delaying abstraction, and even embracing temporary duplication, can save significant development time and prevent future headaches.

Building an abstracted component isn't just about the initial coding effort; it introduces a higher cost of coordination. While removing duplicate code might seem like a win, a wrong abstraction can be far more expensive and time-consuming to unravel than simply deleting some copied lines.

The Lure of Early Abstraction

Consider a common scenario: you have multiple workflows that, on the surface, appear very similar. For instance, imagine three distinct processes: approving an item, changing an item's settings, and moving an item between managed areas. Each of these 'item actions' might involve an item list, selection, a confirmation pop-up, and result toasts. Given these identical UI elements, the immediate inclination is to create a single, generic ItemAction component to handle all three.

This initial assessment, however, often overlooks critical underlying differences. The subtle nuances of how each process handles partial failures, its specific requirements, and the auditing information it needs can vary significantly. An item approval might be a forward-looking assertion, a modification might be instant, and a relocation could depend on an external system's acceptance, which might fail. These fundamental differences, though not immediately apparent, are where premature abstraction begins to break down.

The Pitfalls: Feature Flags and Complexity Bloat

When an abstraction is built on superficial similarities, it quickly becomes burdened by the very differences it initially ignored. Our ItemAction component, starting simple, might soon acquire a range of conditional properties to accommodate its divergent use cases.

Imagine the component's properties evolving to include:

  • item: The core data object.
  • mode: A discriminator like "approve", "change", or "move" to specify the workflow.
  • variant: To control rendering, perhaps "inline", "dialog", or "bulk".
  • showDetails: A boolean to conditionally display additional information.
  • requireReason: To prompt for a justification in certain scenarios.
  • onSpecialCase: A callback to handle unique conditions, perhaps only relevant for the "move" mode.

The ItemAction function itself then becomes a labyrinth of if (mode === "move" && onSpecialCase?.(item)) conditions, needsReason = mode === "change" || requireReason logic, and separate submission paths for each mode, each with its own error handling. Every new requirement for one workflow necessitates a new flag, or worse, a modification to shared behavior that then needs to be rigorously verified across all other modes.

This flag-driven complexity leads to significant overhead. A minor change to the confirmation procedure for approvals can no longer be done by the approval workflow's owner alone. It now requires consensus from the owners of all three workflows, review by someone familiar with every possible mode combination, and extensive testing for each permutation of mode and variant. What started as an elegant solution quickly becomes a source of technical debt, slowing down development and fostering conflict.

Why This Happens: The Unforeseen Future

The fundamental reason this trap is so common is that abstraction is often based on the current state of information. We identify patterns and similarities based on what we know today. What we cannot foresee are the future distinctions: a new policy, an unexpected audit finding, or a new integration with a third-party system that behaves differently from everything else. These future requirements will expose the shortcomings of an abstraction built purely on present-day commonalities, leaving no choice but to add more flags and special conditions.

The Rule: Wait for Three, Then Check the Reasons

To mitigate this, a guiding principle is the “Rule of Three,” attributed to Don Roberts and popularized by Martin Fowler in Refactoring. The first time you encounter a pattern, implement it directly. The second time, you might wince but duplicate it again. Only the third time should you consider refactoring and abstracting. However, this rule comes with a crucial caveat: the pattern must repeat for the same reason.

Two pieces of code share the same reason for change if a single decision, made by a single individual or team, would cause both to be modified simultaneously. Consider these examples:

  • formatExpiry(item.expiresAt): If your product team decides to change how dates are formatted across the application, this line in multiple places (e.g., approve, change, move screens) would all change together. This is a good candidate for abstraction.
  • await api.post("/approve", body); toast.success("Done"); vs. await api.post("/change", body); toast.success("Done");: While the code looks identical, the "/approve" endpoint's behavior changes with approval policies, while "/change" changes with validation rules. These are different reasons for change. Combining them prematurely would tightly couple unrelated concerns.

My personal test is: "If a requirement changes for this code, who would I consult, and would all existing copies of this code logically change at the same time?" If the answers differ for each copy, you're dealing with coincidental similarity, not true duplication ripe for abstraction. Sandi Metz articulates this beautifully, stating that duplication is far cheaper than the wrong abstraction. Kent C. Dodds' AHA (Avoid Hasty Abstraction) programming principle echoes this sentiment.

Where Reuse Does Pay Off

This isn't an argument against all abstraction. Some components are inherently stable and beneficial to share from the outset:

  • Primitives that stay stable: Fundamental UI elements like buttons, text inputs, checkboxes, or basic layout containers. They have simple, well-defined interfaces and contain minimal business logic.
  • Design tokens: Colors, spacing units, typography scales. Their very purpose is to ensure consistency across the entire user interface.
  • Accessibility behavior: Complex patterns like focus trapping in modals, robust keyboard navigation, or proper ARIA labeling. Getting these right once and reusing them ensures a consistent, accessible experience.
  • Anything driven by an external standard: Date and time formatting utilities, currency converters, or protocol clients (e.g., for APIs). Their shape and behavior are defined externally, making them less susceptible to internal business logic changes.

A good “smell test” for an abstraction is its list of properties. If the props primarily describe what should be rendered (e.g., children, size, tone), it's likely a robust abstraction. If they describe in what situation the component is being used (e.g., mode, variant, isSpecial), you've probably combined too many distinct concerns.

How to Recover from a Premature Abstraction

If you find yourself with an overly complex, flag-laden component, the recovery path is to reverse course. Start by inlining the abstracted module back into each of its call sites. Copy the common code to each workflow, and then, crucially, remove all the branches and conditional logic that are irrelevant to that specific workflow. This 'retrograde' step temporarily increases duplication but results in shorter, easier-to-understand, and independently modifiable codebases for each workflow.

Once these separate workflows stabilize and evolve independently, you can then revisit them. Look for patterns that genuinely repeat for the same reason across these now-distinct implementations, such as a specific table rendering logic or date formatters, and extract only those truly common, stable pieces into new, smaller, and more focused abstractions.

The Rule, Once More

Copy and paste until the same pattern presents itself three times for the same reason. However, always prioritize extracting primitives, design tokens, accessibility features, and standardized utilities first, as these are inherently stable and designed for widespread reuse.

So, what's that piece of code in your project that people tiptoe around, the one with a dozen special flags for unique callers? It might just be a candidate for de-abstraction.

FAQ

Q: What exactly is meant by "same reason for change"? A: "Same reason for change" means that if a business requirement or design decision were to change, all instances of the duplicated code would logically need to be updated simultaneously because they serve the exact same underlying purpose. For example, if a company-wide date format policy changes, all formatDate() calls would change. If one toast.success() is tied to an approval policy and another to a validation rule, their reasons for change are independent, even if their syntax looks identical.

Q: How does this approach impact development speed in the short term? A: In the very short term, it might feel slightly slower as you're duplicating code instead of immediately abstracting. However, this initial 'slowness' is often recouped rapidly by avoiding the significant overhead of managing complex, conditional components. Developers can work independently on features without coordinating changes across multiple workflows, leading to faster delivery and fewer bugs caused by unintended side effects in a shared, over-abstracted component.

Q: When is it always safe to build a reusable component from day one? A: It's generally safe to build reusable components from day one when their interface and behavior are stable, simple, and not dictated by specific business logic. This includes basic UI primitives (buttons, inputs), design system tokens (colors, spacing), core accessibility features (dialog focus management), and components driven by external standards (currency formatting, protocol clients). These components describe what they are, not in what situation they're used, making them highly reusable.

#programming#Stack Overflow Blog#cc-by-sa#contributed#building-software#frontendMore

Related articles

Customer Support: AI's Gnarliest Problem and Engineering's Challenge
Programming
Stack Overflow BlogNov 10

Customer Support: AI's Gnarliest Problem and Engineering's Challenge

Customer support presents a formidable challenge for AI, labeled as 'uniquely gnarly' by Airbnb's CTO Ahmad Al-Dahle. This complexity stems from the nuance of human language, emotional context, dynamic problem spaces, and multi-faceted issues. To tackle this, innovative approaches like deploying multi-party AI agents are emerging, requiring engineers to rigorously evaluate and balance crucial trade-offs between latency and accuracy, all while maintaining a high level of judgment.

ai: Crusoe sells troubled Wyoming data center project to Google
Tech
TechCrunch AINov 7

ai: Crusoe sells troubled Wyoming data center project to Google

Crusoe, the $30.9 billion data center developer, has sold its unfinished Wyoming data center project to Google. The tech giant is taking over the development after concerns about costs and delays, underscoring the immense challenges in building AI infrastructure. Despite this setback, Crusoe continues to secure major deals and raise significant funding.

Mastering shadcn/ui for React Native: A Developer's Handbook
Programming
freeCodeCampNov 5

Mastering shadcn/ui for React Native: A Developer's Handbook

Building React Native applications often leads to a familiar challenge: what starts as a concise colors.ts file and a simple Button component gradually balloons into a sprawling, difficult-to-maintain codebase. Design

Understanding CAPTCHA Farm Attacks: A Developer's Guide
Programming
freeCodeCampNov 5

Understanding CAPTCHA Farm Attacks: A Developer's Guide

CAPTCHA farm attacks leverage human solvers to bypass automated verification challenges, making bots appear as legitimate users. This article details the four-part infrastructure and step-by-step execution of these attacks, highlighting why human adaptability, cost-effectiveness, and network decoupling make them difficult to detect. Developers need a defense-in-depth strategy beyond basic CAPTCHAs.

Detecting Financial Fraud with Knowledge Graphs: A Guide
Programming
freeCodeCampOct 30

Detecting Financial Fraud with Knowledge Graphs: A Guide

This guide demonstrates how knowledge graphs, powered by graph databases like Neo4j, are superior for detecting sophisticated financial fraud patterns that traditional rules-based systems miss. By modeling financial entities and their relationships as a graph, and leveraging materialized derived edges, developers can efficiently uncover complex schemes like money mule rings and vendor collusion. The approach enhances fraud detection by focusing on network topology and relationship-based insights.

Stack Overflow's Evolution: Beyond Q&A in the AI Era
Programming
Stack Overflow BlogOct 30

Stack Overflow's Evolution: Beyond Q&A in the AI Era

Stack Overflow is not dying, but evolving amidst the rise of AI. While developers may visit less for simple queries, the platform remains crucial for advanced questions and AI answer verification. The company is pivoting to become a comprehensive knowledge infrastructure, powering AI training, offering enterprise solutions like Stack Internal, and enabling AI-native knowledge exchange through Stack Overflow for Agents.

Back to Newsroom

Stay ahead of the curve

Get the latest technology insights delivered to your inbox every morning.