What is a Mental Stack?
I enjoy playing fighting games like Street Fighter. There’s a concept prevalent in Street Fighter (and fighting games generally) called the “Mental Stack”.
The “Mental Stack”, as defined by Yassin Hussein at Redbull ESports is “how much a player needs to keep track of during the match”. The current release Street Fighter 6 has a noticeably taller mental stack than previous iterations of the series, with a significantly higher amount of mechanics, special moves, offensive and defensive strategies than the “Super Turbo” arcade cabinets of yore. As the redditor /u/AstronomyTurtle describes, “I feel like I’m playing a SF game, but with like 60% of another fighting game tacked onto it, because that’s how much mental stack there is.”
The Software Engineering Mental Stack
The more I thought about this concept of a “mental stack” in my free time, the more I found it applicable in my professional experience. Software, similar to Street Fighter, absolutely has a mental stack. More importantly, technologists who are making decisions for large organizations need to understand the opportunity cost of the “mental stack”.
A Frontend Mental Stack
Let’s say a frontend team working for ACME Corp is going to be building an e-commerce application. The team will have all of their APIs built for them and be delivering only the external frontend.
Web development is rife with churn and change. As of the time of writing, the stack may look like this:
react
for creating reactive user interfacestailwindcss
for stylingheadlessui
for componentsvite
for build toolingi18next
for internationalization
This stack is already starting to look quite involved, even if most of these packages are becoming ubiquitous in the frontend space.
However, technical leaders still have to make decisions for remaining parts of the stack. Should one choose a “static-only” single page app? Or maybe something like Next.js with a server rendering component?
The technology leaders at ACME corp could choose vite
to build a single-page webapp (SPA). At first glance, this option seems to yield the simplest mental stack. After all, this project won’t need any infrastructure other than static hosting. However, in my experience the static site frontend tooling quickly reaches mental stack escape velocity.
The mental stack for a vite app might look like this:
@tanstack/react-query
for querying backend APIs, caching, pagination, in-memory store, memoizationreact-router
for client side routingreact-hook-form
for managing form statereact-use
for even more react hook functionalityxstate
for state management
A single webapp may use a subset of these elements or it may use more, but looking at this list the mental stack for this team is quite large. Each one of these packages requires an individual developer to comb through documentation, GitHub issues or blog posts to use correctly. There’s no doubt that these packages provide value, but in my experience, the more packages a developer puts on their mental stack, the less they are able to dedicate to the actual building of their product. Not to mention, the mental stack for a single page webapp might be holistically different a year later.
A mental stack that focuses on fundamentals, can leave more of a developer’s mental capacity to focus on the product and their customers. It’s no secret that every package added adds time, energy and overhead. A complex mental stack can slowly creep up on a team, and bury them in “Keep the Lights On” activities.
Some frontend frameworks like Remix laude themselves on building on web fundamentals, and in the Remix case you can drop some of the above for native HTML <form>
and fetch()
. However, multi-page application frameworks like Remix and Next.js trade client-side simplicity for server-side infrastructure and complexity. One project I worked on chose to use Next.js but the developers struggled to understand the value or use-cases of the server, and as such were client rendering every view in the app. In this case, the mental stack was too high, with too many moving parts such that the productivity and the product suffered.
A Backend Mental Stack
You can repeat this mental stack exercise ad nauseam. In the case of the backend, a medium to large startup might end up with a mental stack of something like this:
graphql
APIs built with@apollo/server
for frontend queriesrest
APIs built with@nestjs
for service to service domain exposureflask
REST APIs built by data science teamskafka
topics and queues for domain eventspostgres
SQL databases for persistencerabbitmq
for asynchronous messagingredis
for caching/temporary storagedocker
for running your application in a containerkubernetes
for deployment/running containershelm
for packaging thekubernetes
manifests
This mental stack adds up quickly! We’re asking a lot of developers - specialists or not - to be able to wrap their mind around the entirety of the stack. This can become daunting for onboarding engineers quickly and maintaining engineering velocity.
Similar to an overabundance of frontend packages burying a team in “Keep the Lights On” activities, backend infrastructure can quickly become a burden. In my experience, the more infrastructure a team has to manage, the more time they spend on infrastructure and less time they spend on the product and delivering true value.
Some companies will outsource this infrastructure to separate teams, or specific individuals. This can lower the maintenance and operational overhead on the development team, but does little to actually reduce the ability for a team of individuals to “grok” or understand the system.
Should you Minimize the Mental Stack?
A reasonable conclusion might be to minimize the mental stack as much as possible.
Perhaps instead of installing a myriad of npm
dependencies, you opt to write your own component library, css processor, and JavaScript bundler. Obviously this doesn’t solve any appreciable problem, and you are left with a mental stack rife with custom solutions that will likely be more difficult to comprehend.
Instead, the conclusion I’ve made when thinking about the mental stack of software is one of tradeoffs.
Everything you add into your project or company, every dependency, every infrastructure provider, every framework and every paradigm has a contribution to the mental stack. Technologists at your company have a responsibility to make sure the benefits of this technology are worth the mental stack contribution.
Some examples you can ask yourself when evaluating “the mental stack”
- Does your technology make it easier to understand the system or more difficult?
- Does the technology have exhaustive documentation?
- Does the technology have readily available source code?
- How difficult is it to find or attract developer talent with experience in the technology?
- How mature is the ecosystem surrounding the tool?
- Will my developers understand the trade-offs we’re making by choosing this technology?
These questions are a useful exercise before making any large scale technology decision. I’ll use two examples from my professional life as case studies of this concept.
Case Study: Kafka
Thinking through this trade off - let’s discuss Apache Kafka.
Apache Kafka is an open-source distributed event streaming platform used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications.
I’ve worked on two implementations of Apache Kafka in my professional life. In one, Kafka was well worth the overhead, providing a way to manage a huge number of (slow) transactions asynchonously. The mental overhead of our (small) team of developers to manage the requisite infrastructure was worth it for the tradeoffs we made. What made this implementation successful, was using Kafka for a narrow and specific use case. We understood the technology, and everyone on the team knew “why” we were using it. There was no question we we’re using the right tool for the job.
In the other implementation, the complete opposite was true. In this instance, an inordinate amount of Kafka topics, connectors, producers, and consumers slowed the development team to a halt. The product suffered from user facing “lag” and our hands were tied. In this case, from my perspective, we lost sight of the “why” of the technology, and what we were gaining for the increase in complexity.
Success and failure with a technology like Kafka is not binary. It’s not a question of “should we use Kafka or not”. It’s a question of “is the mental stack worth the benefits of using Kafka?”.
Case Study: Helm
Helm touts itself as the “package manager for Kubernetes”. Helm itself is a great tool, but it’s not without its rough edges. For one, it is easy for those less experienced in writing “Kubeyaml” to get losts in its layers and layers of indirection. In addition, our team has struggled to understand some of its more archaic error messages.
As opposed to the case study above however, I would say by and large Helm was worth the mental stack. You take the “mental stack” overhead from the rough edges above, and in exchange Helm gives you consistent, repeatable lifecycle management for Kubernetes. Building and deploying your “Kubeyaml” from scratch might take away the overhead of indirection in values.yaml
files but it will mire you in deployment/rollback management, and standardization of your manifests.
It is easy to lose sight of the “why” of Helm, but it does a good job of providing net positive value for the mental stack it adds.
Conclusion
In my view, each and every technology assessed by your engineers and engineering leadership should be evaluated on this spectrum. Asking the question: “is what I’m doing/adding/removing worth the engineering mental stack?” will benefit not just yourself but your whole team.
Is Simpler Better?
Some would say yes, as certain people love Street Fighter II. The purists that say the addition of parrying, super moves, and long combos hurt the spirit of the original Street Fighter. Some will say that adding in the boss characters like Vega and M. Bison killed the spirit of this series. Others argue Street Fighter 6 is the best iteration despite having more mechanics and an undoubtedly higher mental stack.
Similarly, the engineering mental stack affects all individuals differently. Your team may have a plethora of experience across technologies within your stack that lends to a polyglot of tooling and indexing towards the “right tool for the job”. On the other hand, a team might consist of developers who have specific expertise so sticking to certain toolchains/frameworks/languages may pay dividends for your team’s productivity.
Bottom Line
Software decisions are not free. Using a new language or tool does not come without tradeoffs. If your team’s velocity, capacity, or productivity is struggling, look to the mental stack and try to discern if the benefits of your technology decisions are worth their mental stack contributions.
Special thank you to my coworker Lauren Dumler for helping me refine this idea and proofreading the early drafts!