Mayur Dhaka
Dec 23, 2025
- Differences in Programming for a Library vs. an Application
- Thinking About Edge Cases More Rigorously
- Error Handling is About Reporting, Not Reacting
- application.py
- Somewhere in the application flow
- library.py
- Let Callers Choose the Integration Model
- Measure Twice (and Maybe Thrice), Cut Once
- Document Document Document!
- Conclusion
Differences in Programming for a Library vs. an Application
![][assets/blog/differences-programming-library-vs-application/cover-image.png]
When you write an application, you control the flow. When you write a library, the flow controls you.
Though I have no numbers to support my claim, I would guess there are far more programmers writing applications that use libraries (small or large) than there are programmers writing libraries. Inevitably, the application programmer finds themselves—at some point in their career—writing library code…and it's a whole different ride.
Library programmers need to be far more rigorous, and far less opinionated, than their application programmer peers. Here's a more detailed list of differences:
- Thinking about edge cases more rigorously
- Error handling is about reporting, not reacting
- Let callers choose the integration model
- Measure twice (and maybe thrice), cut once
- Document document document!
Let's dive deep.
Thinking About Edge Cases More Rigorously
Of course every programmer needs to handle faulty input. But in programming for a library, your code could impact hundreds to thousands of applications. And each application could have that many more users.
Therefore, by definition, you will have to guard against a far larger number of edge cases than just the applications themselves. Take the case of the React bug that happened recently at the time of this writing. It gave hackers access to run remote code on machines that ran the affected version of React (estimated to around 40% of cloud environments).
As an example, most application developers that deal with storage to disk, typically don't concern themselves with the user's device not having enough storage space. This is why a lot of apps don't surface "you're out of disk space" messages. Libraries, however, do need to think about this kind of edge case and surface it correctly to the caller–even if the caller chooses to not handle it gracefully.
Error Handling is About Reporting, Not Reacting
When writing libraries, dealing with an error is really out of your hands. In most cases, you must always report back to the calling code.
Take the example of this simple Python code that reads from the file system. When writing in an application you could get away with:
# application.py
def read_file(path):
try:
# Open the file
# Read its contents
return file_contents
except Exception:
# Application decides what to do on failure
# e.g. print an error, show a message, exit, retry, etc.
print("Failed to read file")
return None
# Somewhere in the application flow
data = read_file("/path/to/file")
if data is None:
# Application-specific fallback behavior
print("Data or file not found.")
Whereas in a library, you need to let the caller decide what they want to do when such errors occur. The same example from above would map into the following illustrative library code:
# library.py
class FileReadError(Exception):
pass
def read_file(path):
try:
with open(path, "r") as f:
return f.read()
except OSError as exc:
raise FileReadError(f"Failed to read file: {path}") from exc
In this way, the application developer is in control of how they want to deal with the situation when an error occurs, based on their application's needs.
Let Callers Choose the Integration Model
Applications and libraries can both embody strong opinions—and many good ones do. The difference is not whether opinions exist, but who gets to act on them.
Application code owns its execution environment. It can assume a specific runtime, concurrency model, deployment setup, and user experience. Because of that, applications are free to bake decisions directly into the code: how errors are handled, how work is scheduled, how configuration is loaded, and how results are presented.
Library code does not have that luxury. A library is invoked by someone else's code and runs inside an environment it does not control. For that reason, library code should focus on providing mechanisms rather than enforcing policies. The library exposes capabilities; the application decides how, when, and under what constraints those capabilities are used.
Because libraries are consumed by many applications with vastly different requirements, they often need to support multiple integration styles. This flexibility isn't about being non-opinionated—it's about avoiding assumptions about the caller's runtime, concurrency model, or execution flow.
Measure Twice (and Maybe Thrice), Cut Once
Application developers think heavily about what their fellow programmers would do when using code they write. And of course, they need to think very carefully about how users of their software would use the app they're building.
A library programmer, on the other hand, needs to be thorough with how they structure and write their code. As a library programmer, if you have a designated way in which your code should be called, you absolutely need to guard against users (of your library) not respecting that order, or, more likely, misunderstanding the usage of your library. Worse, your code is just as susceptible to bugs in the caller's code, rather than just yours.
And in such cases, you can't really just go tell the caller "Don't use it that way". You need to be absolutely sure about the code you're writing and whether it communicates to the user the misuse that occurred.
Lastly, every change you publish, needs to go through a strict version upgrade–typically the semantic versioning format. And you need to publish release notes, along with migration guides, thinking about users upgrading from older versions of your libraries. Whereas for the application developer such considerations are hyper-localised to their applications.
All this amounts to being very, very sure about the code you're writing.
Document Document Document!
This one is self-evident: application developers can get away with not having to document much of their code. But with library authors, having great documentation–even if it's repetitive–can be the difference between someone adopting your library (because it's easy to understand and meets the users where they are rather than forcing them to adapt to you) or moving to alternatives.
In practice, this could mean documenting each and every constructor of a class–rather than relying on a "go look there" approach, since users would call different constructors and expect their editors to surface your documentation where they see fit–rather than being forced to go look at a certain location.
Conclusion
Writing a library isn't just about writing reusable code—it's about giving up control. Unlike applications, libraries live in environments you don't own, are used in ways you can't predict, and break out of sight when they fail.
That reality demands a different mindset: fewer assumptions, clearer contracts, careful versioning, and obsessive documentation. Good libraries often feel cautious and unexciting—and that's exactly the point.