<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/the-complexity-you-remove-from-your-library-doesn-t-disappear-it-moves--ojhxwoqtz" -->

---
title: The complexity you remove from your library doesn't...
description: Removing complexity from a library doesn't eliminate it, it just relocates it to clients, thread pools, or latency. Using a C# message-publishing component as...
canonical: https://daily.dev/posts/the-complexity-you-remove-from-your-library-doesn-t-disappear-it-moves--ojhxwoqtz
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: The complexity you remove from your library doesn't disappear. It moves. | daily.dev
og:description: Removing complexity from a library doesn't eliminate it, it just relocates it to clients, thread pools, or latency. Using a C# message-publishing component as...
og:url: https://daily.dev/posts/the-complexity-you-remove-from-your-library-doesn-t-disappear-it-moves--ojhxwoqtz
og:image: https://api.daily.dev/og/posts/OJHxwOqTZ.png
og:image:alt: The complexity you remove from your library doesn't disappear. It moves.
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The complexity you remove from your library doesn't disappear. It moves.

**[The Polymathic Engineer](https://daily.dev/sources/francofernando)** · 19 min read · 2 upvotes · 1 comments

## Summary

Removing complexity from a library doesn't eliminate it, it just relocates it to clients, thread pools, or latency. Using a C# message-publishing component as an example, abstracting a metrics dependency behind an interface simplifies the library but forces every client to implement that interface. Hooks let clients inject custom logic but require try-catch wrapping and a dedicated thread pool, and they add latency at least equal to the slowest hook. Listeners avoid that wait but need immutable state and backpressure handling. A good middle ground is often an abstraction with a default implementation.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://newsletter.francofernando.com/p/the-complexity-you-remove-from-your>

## Questions this post answers

### What's the difference between a hook and a listener in an API design, and when should I use each?

A hook is client code the component calls synchronously at a point in its lifecycle and waits for, because the next phase depends on data the hook may modify; a listener is notified asynchronously with no happens-before relationship, so it adds no latency. Use hooks when downstream logic needs the hook's result, and listeners when clients only need to be informed of an event.

_Weighing hooks versus listeners for your API? daily.dev surfaces design trade-off discussions like this one._

### How much latency does a blocking synchronous hook add to an API call if the hook calls a remote service?

If the main operation normally takes 300 ms and a hook performs a remote signing call taking 1,000 ms, the total becomes 1,300 ms, over 4 times slower, and adding a second similar hook pushes the total to 2,300 ms. Running hooks in parallel on separate threads still adds latency equal to the slowest hook, since the next phase must wait for all hooks to finish.

_Debugging latency from extension points? daily.dev helps engineers track down patterns like this._

### How do I safely pass a mutable list to listener callbacks without one listener corrupting data for another?

Wrap the list in a read-only view, such as calling AsReadOnly() in C#, before passing it to listeners. This avoids copying the data while throwing an exception immediately if a listener tries to modify it, making bugs fail fast instead of silently corrupting a defensive copy that other listeners never see.

_Designing listener APIs that stay safe under misuse? daily.dev tracks practical patterns like this for backend engineers._

## Community discussion

Top comments from developers on daily.dev.

**@laouadirabah** · 1 upvotes

> This is the part people usually miss: simplifying the API doesn’t necessarily simplify the system, If the complexity comes back as retries, backpressure, latency, or failure handling, you’ve mostly moved the cost somewhere else.
>
> A good abstraction should hide implementation details, not operational consequences.

## Similar posts on daily.dev

- [Abstractions That Heal, Abstractions That Harm](https://daily.dev/posts/abstractions-that-heal-abstractions-that-harm-byjb3ozq1) · We Are .NET · 58 upvotes · 3 comments
- [Abstraction Is Not Merely Hiding Complex Code](https://daily.dev/posts/abstraction-is-not-merely-hiding-complex-code-tvfkgxuc9) · Medium · 0 upvotes · 1 comments
- [The 'Hidden' Costs of Great Abstractions](https://daily.dev/posts/the-hidden-costs-of-great-abstractions-u2wlkrp6a) · Hacker News · 1 upvotes · 0 comments
- [When to repeat yourself](https://daily.dev/posts/when-to-repeat-yourself-c67y3splj) · The Polymathic Engineer · 175 upvotes · 7 comments

---

Tags: [#architecture](https://daily.dev/tags/architecture), [#c#](https://daily.dev/tags/c#)

[View this post on daily.dev](https://daily.dev/posts/the-complexity-you-remove-from-your-library-doesn-t-disappear-it-moves--ojhxwoqtz)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"The complexity you remove from your library doesn't disappear. It moves.","url":"https://daily.dev/posts/the-complexity-you-remove-from-your-library-doesn-t-disappear-it-moves--ojhxwoqtz","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/the-complexity-you-remove-from-your-library-doesn-t-disappear-it-moves--ojhxwoqtz"},"datePublished":"2026-10-30T09:45:45.637Z","dateModified":"2026-10-30T09:46:17.876Z","description":"Removing complexity from a library doesn't eliminate it, it just relocates it to clients, thread pools, or latency. Using a C# message-publishing component as...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/27e95c27948badaa4817bd3aa4b41eab?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/27e95c27948badaa4817bd3aa4b41eab?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"The Polymathic Engineer","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"The Polymathic Engineer","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/2a66d4e9f17e4214a6d620baed8123e1","url":"https://daily.dev/sources/francofernando"},"commentCount":1,"discussionUrl":"https://daily.dev/posts/the-complexity-you-remove-from-your-library-doesn-t-disappear-it-moves--ojhxwoqtz","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":1}],"keywords":"architecture,c#","timeRequired":"PT19M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"The Polymathic Engineer","item":"https://daily.dev/sources/francofernando"},{"@type":"ListItem","position":3,"name":"The complexity you remove from your library doesn't disappear. It moves."}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/the-complexity-you-remove-from-your-library-doesn-t-disappear-it-moves--ojhxwoqtz","comment":[{"@type":"Comment","text":"This is the part people usually miss: simplifying the API doesn’t necessarily simplify the system, If the complexity comes back as retries, backpressure, latency, or failure handling, you’ve mostly moved the cost somewhere else.\nA good abstraction should hide implementation details, not operational consequences.","datePublished":"2026-10-30T20:55:03.415Z","url":"https://daily.dev/posts/OJHxwOqTZ#c-BX2yNCPbw","author":{"@type":"Person","name":"Laouadi Rabah","url":"https://daily.dev/laouadirabah","image":"https://avatars.githubusercontent.com/u/297700963?v=4"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/the-complexity-you-remove-from-your-library-doesn-t-disappear-it-moves--ojhxwoqtz#faq","mainEntity":[{"@type":"Question","name":"What's the difference between a hook and a listener in an API design, and when should I use each?","acceptedAnswer":{"@type":"Answer","text":"A hook is client code the component calls synchronously at a point in its lifecycle and waits for, because the next phase depends on data the hook may modify; a listener is notified asynchronously with no happens-before relationship, so it adds no latency. Use hooks when downstream logic needs the hook's result, and listeners when clients only need to be informed of an event. Weighing hooks versus listeners for your API? daily.dev surfaces design trade-off discussions like this one."}},{"@type":"Question","name":"How much latency does a blocking synchronous hook add to an API call if the hook calls a remote service?","acceptedAnswer":{"@type":"Answer","text":"If the main operation normally takes 300 ms and a hook performs a remote signing call taking 1,000 ms, the total becomes 1,300 ms, over 4 times slower, and adding a second similar hook pushes the total to 2,300 ms. Running hooks in parallel on separate threads still adds latency equal to the slowest hook, since the next phase must wait for all hooks to finish. Debugging latency from extension points? daily.dev helps engineers track down patterns like this."}},{"@type":"Question","name":"How do I safely pass a mutable list to listener callbacks without one listener corrupting data for another?","acceptedAnswer":{"@type":"Answer","text":"Wrap the list in a read-only view, such as calling AsReadOnly() in C#, before passing it to listeners. This avoids copying the data while throwing an exception immediately if a listener tries to modify it, making bugs fail fast instead of silently corrupting a defensive copy that other listeners never see. Designing listener APIs that stay safe under misuse? daily.dev tracks practical patterns like this for backend engineers."}}]}
```

