Every layered system, whether it is a network, an operating system, or cloud infrastructure, has to answer the same question: where should each function live? Should encryption happen in the network or the application? Should retries happen in TCP or in your code? Should duplicate suppression happen in the transport layer or the endpoint?

The end-to-end argument, laid out by Saltzer, Reed, and Clark in 1984, is one of the most influential answers to that question. The short version: many functions belong at the application endpoints, not in the lower layers, because the lower layers usually can't do them completely enough to remove the need for the application to do them anyway.

In this article, I'll walk through what the end-to-end argument actually says, why it matters, and where it holds up today.

This post is a summary and critique of the original paper: "End-to-End Arguments in System Design" by J.H. Saltzer, D.P. Reed, and D.D. Clark, published in ACM Transactions on Computer Systems, 1984. It can be accessed here.

The Core Argument

The central thesis of the paper is deceptively simple: Functions placed at low levels of a system may be redundant or of little value when compared to the cost of providing them at that level.

In a layered system (like the OSI model), it is tempting to put as much functionality as possible into the lower layers (like the data link or network layers) to make them smarter. Proponents of this approach argue that if the network itself can ensure data is delivered reliably, the application doesn’t need to worry about it. It should be faster, right?

Saltzer, Reed, and Clark argue the opposite. They suggest that because the network is unreliable by nature, relying solely on the network for reliability is often impossible. Even if the network promises delivery, errors can occur at lower levels due to hardware failures, packet loss, or corruption. Therefore, the ultimate responsibility for correctness like ensuring a file isn't corrupted or verifying a transaction is best pushed upward to the application endpoints.

This is the essence of the End-to-End Argument: If the application at the destination must verify the correctness of the data, a lower-layer guarantee of correctness is necessarily redundant. You end up paying the cost twice.

Where the Argument Applies

The paper walks through several examples, and they're all worth knowing.

Reliable delivery. If the application checks whether the message arrived correctly (via acknowledgments and checksums), the network doesn't need to guarantee reliability. Best-effort delivery is enough.

Encryption. If the application encrypts data before handing it to the network, no lower layer needs to. And only the application can encrypt end-to-end, because the network doesn't know who the "real" endpoints are.

Duplicate suppression. If the application uses request IDs to detect duplicates (idempotency), the network doesn't need to guarantee exactly-once delivery.

FIFO ordering. If the application needs strict order, it can number messages and sort them at the receiver. The network doesn't need to preserve order.

Transaction management. Commit/rollback semantics need application-level knowledge of what "done" means. Lower layers can't provide it.

In every case, the pattern is the same: the endpoint has to do the work anyway, so pushing it lower buys little and costs more.

Why This Matters

The end-to-end argument is one of the reasons the internet works the way it does. IP is a simple, best-effort protocol. It doesn't guarantee delivery, ordering, or reliability. TCP layers reliability on top when applications want it. UDP skips reliability entirely for applications that don't.

That layering is a direct application of end-to-end thinking. The network stays simple and fast. Applications add exactly the guarantees they need, and nothing more. If IP had tried to be reliable at the network layer, we probably wouldn't have real-time voice and video, because a reliable network layer punishes latency-sensitive apps that don't need retries.

The same argument shows up outside networking. A subway system that charges you when you enter the station, rather than on each train, pushes the collection to the "endpoint" of the journey. That lets you switch trains freely without repaying. A CPU that trusts the operating system to check permissions, rather than checking on every instruction, is applying end-to-end thinking to hardware.

Once you see the pattern, you notice it everywhere.

It's a Guideline, Not a Law

The paper is careful to note that the end-to-end argument is a guideline, not an absolute rule. There are real reasons to put functions at lower layers.

The paper acknowledges that lower-layer optimizations can provide performance benefits. If a function at a low level can significantly increase the probability of success without being perfectly redundant (for example, caching or compression), it may be worth implementing there. The key is to recognize that while lower-layer functions can help performance, they cannot substitute for end-to-end correctness.

However, the paper does have some limitations. Written in the context of the early internet (ARPANET era), it focuses almost exclusively on data communication as an illustrative example. It doesn't clearly delineate how to identify endpoints in modern microservices architectures, where a  "function" might span multiple containers or cloud providers. Furthermore, the discussion on two-phase commit protocols (essential for distributed transactions) could have been deeper, leaving readers to wonder how strict consistency fits into the End-to-End framework.

Modern Relevance: Does It Still Hold?

Decades after publication, the debate continues. A common discussion point arises: Where do UDP and TCP fit within this argument? TCP is often viewed as the embodiment of the End-to-End argument applied at the transport layer. It ensures ordered, reliable delivery, acknowledging that the underlying IP (Internet Protocol) is dumb and unreliable. In a sense, IP was designed to be minimal (pushing complexity up), which aligns with the End-to-End philosophy. One might argue that the design of the internet itself was motivated by this argument, or that the argument emerged from observing that the IP design worked because the complexity lived at the edges.

Today, with faster processors and higher-speed networks, the cost of computing checks and balances is lower. Does this invalidate the claim? Surprisingly, no. While bandwidth is cheap, the cost of security breaches and data loss has skyrocketed. As we move toward serverless architectures and containerization, the "ends" of our systems are more distributed than ever. Ensuring integrity across distributed nodes remains an end-to-end concern.

Conclusion

The End-to-End Argument is not just about networking, it is a mindset for system design. It teaches us to distinguish between what a system can guarantee and what a system should enforce. By respecting the principle of pushing functionality to where it adds the most value we can build systems that are simpler, more secure, and more resilient.

From here, read the original 1984 paper if you haven't. It's short, clear, and full of examples. Then look at a real system you use, whether TCP, HTTPS, or a message queue, and ask which functions live at which layer, and why. You'll start seeing the same design pattern everywhere. Happy reading!