Front-end maximalism
Here's a question that comes up all the time:
Q: I have a front end that calls into a back end. It needs to do things now, and might need to do more things later. How much filtering and preprocessing should the back-end do before it passes the data to the front end?
And here's an answer I like:
A: As little as possible.
Some examples:
- Suppose you have a product page with a long list of products. The user can select one of the products to view the details. It's tempting to first retrieve the list, then retrieve specific products' details when the selection happens. But it's often better to retrieve all the product details up front and use local state management to determine which details to show.
- Suppose you're building a dashboard so that a coach can see statistics for their players. The coach inputs some players and some filters, and they see the data. Instead of passing the query to the back end and retrieving the filtered data, consider fetching all the relevant data on page load and updating it as the coach enters players and queries.
- Suppose you run a flashcard site and you let users choose subsets of available cards to study. Instead of sending their current study preferences to the back end, fetch most or all of the cards they might want to study now, and only fetch others if they make an unusual request.
Most engineers wouldn't approach the problem like this. I suspect this is largely for psychological and sociological reasons: it's just not how things are done. Things like querying and filtering feel back-end-ish, not front-end-ish, and shipping all relevant data to the front end feels crude. Yet if you gave working engineers a written exam, and if one of the questions asked about the principles one should use to divide work between the back and front ends, I suspect you'd get a lot of answers like these:
- Don't send more data than any part of the system (including the network connection) can handle.
- Minimize code complexity.
- Minimize the complexity of the user flow.
- Maintain encapsulation where possible.
- Don't create security vulnerabilities.
- ...and so on.
Front-end maximalism is the view that considerations like these, under modern conditions and in a wide range of standard applications, should lead us to do much more than we currently do on the front end and much less on the back end.
When not to use a front-end maximalist approach
This is, of course, a general observation, and not at all a universal one. There are two big reasons to keep a lot of data processing on the back end.
First, sometimes there's simply too much data to pass to the front end. Such is life. Here the back end indeed must filter, paginate, truncate, or throttle, as necessary. Before you conclude you need to do this, however, be careful and show your work. I have literally lost count of how many times colleagues have told me that a front-end maximalist solution was data-prohibitive, but the data in question was smaller than that of a standard .jpg file.
It is easy to reject front-end maximalism for scaling reasons: even if there's not too much data now (the argument goes), there will be too much data later--or, at least, there might be. These arguments usually fail. Often there's not even potentially too much relevant data. In the coach - statistics example above, the data relevant to a given user is bounded above by some future total number of players times a maximum amount of data per player in the system. Usually (usually!) this will be well below what you can happily ship from a back end to a front end.
More importantly, but also more controversially, even if you do have good reason to believe your system's scale will one day thwart a front-end maximalist approach, it can still be better to use front-end maximalism now, because of Gall's Law:
A complex system that works is invariably found to have evolved from a simple system that worked.
A complex system designed from scratch never works and cannot be patched up to make it work.
You have to start over with a working simple system.
Even where it won't work forever--and, again, that is rarer than most engineers think--it often works now, and is the simplest and cleanest way to get it working now. That's underrated.
The second main reason to reject a front-end maximalist approach is security. Definitely do not ship data to the front end if the user should not have it. Sending it but not displaying it is a bad compromise: the data still come over the wire, and you have to assume that at least some of your attackers are sophisticated.
Remember, though, that the division of labor between the front and back ends involves security tradeoffs. A front-end maximalist solution can be more secure. As you build more and finer-grained behavior into the back end, the attack surface there increases. If, for example, a user is querying their data on the front end, maliciously constructed filters can only get different subsets of their data. That's often untrue on the back end. All the pagination, querying, filtering, and so on can itself carry security risk.
Supposing you can, in fact, implement a front-end maximalist solution, here are benefits you'll often access:
- Speeding up user experiences.
- Eliminating bugs having to do with pagination, query construction, and other aspects of the standard data-retrieval paradigm.
- Reducing the cost of adding new features (a sort of future-proofing). When the data is already at the front end, and you know it, you'll see easy paths to features you wouldn't even consider otherwise.
- Eliminating API calls.
- Improving privacy, or at least minimizing user data: Insofar as filtering and querying data doesn't touch the back end, there are no back-end traces of it.
Some common objections
- "Do you really think so many people are wrong about this?"
Some form of this is the most common objection I hear: how could standard practice be so wrong?
First, as I've implied above, conventional wisdom is incoherent. Insofar as simplicity and testability are standard tenets, for example, standard patterns of front- and back-end communication are often in violation of standard practice.
Second, front-end maximalism is a lot more viable than it was ten or twenty years ago. I suspect that technology has simply evolved faster than conventional wisdom ever will. Here is an incomplete but illustrative set of influential works:
1994: Design Patterns 1999: The Pragmatic Programmer 2003: Domain-Driven Design 2006: Joel on Software (approximate peak influence) 2008: Clean Code 2018: A Philosophy of Software Design
And here is an incomplete but illustrative set of important technologies that enable a front-end-maximalist approach:
2000: Python 2.0 2009: py 1.0.0 (now "Pytest") 2011: React (first deployment) 2012: TypeScript (initial release) 2014: Jest (open-sourced) 2014: Swift 1.0 2024: DuckDB 1.0.0
So much system design guidance was baked into the culture long before we had the tooling that makes front-end maximalism viable.
- What about people with bad connections? Are you just going to clobber them with data?
Again: if a front-end maximalist approach makes you ship simply too much data to your users, then do not use a front-end maximalist approach.
That said, the front-end maximalist approach is often friendlier to users with worse connections. Those connections are usually not just slower but less stable, and the benefit of making fewer API calls often outweighs whatever benefit you might gain from sending them less data to begin with.
In many applications, a front-end maximalist approach "clobbers" the user, initially, with approximately 2x to 10x as much data. If this is a nontrivial burden to the user--and this is rare, and getting rarer--it's often possible to lighten the payload by other means. (Are you sending fields you never use? Can you truncate UUIDs? Are you representing boolean data with a string that says YES
?)
If you remember nothing else...
- Don't divide responsibilities between the front and the back end just out of habit.
- Doing almost everything on the front end is underrated and increasingly viable.
- Even considering a front-end maximalist approach can lead you to a better design, whatever that design ends up being.
- You can get all the modularity you need by structuring your code appropriately, even if all that code is on the front end.
- Never forget Gall's Law.