Back to posts

Abstraction Tradeoffs

Jon Fu / January 25, 2026

Abstraction Tradeoffs

In software development, abstraction is a concept that allows developers to manage complexity by hiding unnecessary details and exposing only the relevant parts of a system. Think about it as a form of wrapper with a specific set of inputs that does something as output (and hopefully useful).

As a rough analogy, consider a book, which uses "chapters" as an abstraction to organize each chunk of thematically related content. Instead of dealing with every single page or paragraph, readers can navigate through chapters to find the information they need more efficiently.

In software, however, these abstraction "chapters" can be reused in various situations without physical contraints (like trying to reinsert a chapter into another chapter). For example, a function that calculates the area of a rectangle can be reused whenever that calculation is needed, without having to rewrite the logic each time. This also makes that function easier to test and maintain.

So far so good.

Let's say we have a new feature that requires calculating the area of a rhombus or 4-sided polygon. Should we a) write a brand new function for that, or should we b) modify our existing rectangle area function to accommodate this new requirement?

Imagine this evolving over time, where we keep adding more shapes like triangles, circles, and polygons. If we keep modifying our original rectangle function to handle all these shapes (DRY-oriented), it could become bloated and hard to maintain. On the other hand, if we create separate functions for each shape (single-responsibility-oriented), we end up with a lot of redundant code. This concern of DRY-reuse vs. single-responsibility can be seen as how effective the abstraction is, in terms of balancing reusability and maintainability. However, this may not be enough to make the best decision, though it may be the more optimal decision, technically speaking.

In a personal hobby project, the cost and risks of these decisions are relatively low, as you can always decide to unilaterally refactor or rewrite the code as you see fit, at your convenience and schedule. However, in a professional setting with a team of developers, these decisions can have significant implications on code maintainability, readability, and collaboration.

After a year of working as a team lead at an enterprise organization focused on pure application development (not library or SDK development), I've picked up a few "unclean" heuristics to consider when deciding abstraction tradeoffs:

  • Team familiarity: If the team is already familiar with a certain abstraction pattern, it may be more efficient to stick with it rather than introducing a new one that requires additional learning and adaptation. This also depends on how senior the average team experience index is, and how much time leads have to invest in mentoring and onboarding to the new approach.

  • Code readability: Consider how easily other developers can understand the abstraction. A more complex abstraction may be harder to read and maintain, even if it offers better reusability. These days I often prefer this unless there is a strong case for the alternative (e.g. solves a serious/noticeable performance issue).

  • Project consistency: Ensure the abstraction aligns with the existing patterns and conventions in the project. Inconsistent abstractions can lead to confusion and make the codebase harder to navigate. Even if it is suboptimal, sometimes consistency is more important at the expense of other factors.

  • KISS (keep it simple stupid): More times than not, I have to ask my peers to avoid over-engineering abstractions that may not be necessary at the current stage of the project. Sometimes, simpler solutions are more effective in the short term.

  • Time to implement: Estimate how long it will take to implement the chosen abstraction and whether that time investment is justified by the benefits and fit within the allotted time.

Ultimately, the decision to prioritize DRY-reuse or single-responsibility should be made based on the specific context of the project, team dynamics, and long-term maintainability goals. There is no one-size-fits-all answer, and it's essential to weigh the pros and cons of each approach carefully. So the "right" decision often comes down to a mix of technical considerations and human factors, which can be quite subjective.