Hey friends 👋 I hope you’re weekend is going extremely well.
First off, I want to extend the warmest of welcomes to the over 200 of you who have joined Frontend at Scale in the past couple of weeks! I’m so glad you’re here and I really hope you enjoy your very first issue of the newsletter.
If you follow me on Twitter or LinkedIn, you might have seen that I launched a new landing page for my upcoming frontend architecture workshop, which now has an official name: Fundamentals of Frontend Architecture.
I’m really excited about this project, and I’m working hard to share this with you as soon as possible. If all goes as planned (famous last words, I know), I’ll be sharing early access to the workshop with all of you in the next month or so, so look forward to an invite link in a future issue of the newsletter.
We have a great issue today—we’ll be talking about the hardest problem in computer science, what’s new in React 19, and Kent Beck’s learnings from his time at Facebook.
Ready? Let’s dive in!
SOFTWARE DESIGN
Breaking Things Down
I hope you're wearing your thinking hat today because we're about to discuss a very hard problem.
And not just any hard problem, but the hardest problem in computer science.
No, I’m not talking about cache invalidation or naming things. And no, I’m not talking about vertically and horizontally centering an element with CSS, either (I said hard, not impossible.)
The most fundamental problem in computer science, at least as John Ousterhout puts it in his fantastic book A Philosophy of Software Design, is problem decomposition—the ability to break down a large problem into a bunch of smaller ones that can be solved independently.
This is one of those things that are easy to understand but not so easy to put into practice. So today, I thought we’d spend some time talking about why breaking things down is so important, as well as exploring some strategies for how to do it effectively.
Go small or go home
When building software, the problems we have to solve are often bigger than we can fit in our brains. This means that breaking them down into a series of smaller problems is not just convenient—it’s necessary.
Yes, the process of breaking down a large task into a set of smaller ones takes time and effort, but it’s generally worth the investment:
- Smaller tasks are easier to estimate. With a reduced scope, we can more easily define what “done” means and figure out the path to get there. When dealing with small tasks, we’ll be dealing with estimates in hours or days, which tend to be more accurate than estimates in weeks or months.
- Smaller tasks lead to smaller PRs, which are easier to review, less risky, and often speed up cycle time considerably. Small PRs often result in higher-quality reviews as well since developers are much less likely to rubber-stamp an LGTM approval on them.
- Smaller tasks can (sometimes) be parallelized. Depending on how we break things down, some of the pieces could be worked on in parallel by someone else, rather than in sequence by the same person. This type of breakdown is crucial for big tasks or projects that are meant to be tackled by an entire team.
- Smaller tasks are more context-switching-friendly. If you don’t have the luxury of being 100% dedicated to a single project, this is a massive benefit. It’s much easier to switch to a different project after finishing a small task than it is to come back to a half-finished large one.
It is certainly possible to solve a large problem “all at once” rather than piece-by-piece, but in general, trying to do so greatly increases our chances of unexpected setbacks.
We might think that one big 10-size problem would be the same amount of work as ten 1-size ones, but it is actually possible to break things down so that the sum of a problem’s parts would be less effort than trying to tackle the whole thing. Here’s how Kent Beck puts it in Mastering Programming:
“The journeyman learns to solve bigger problems by solving more problems at once. The master learns to solve even bigger problems than that by solving fewer problems at once. Part of the wisdom is subdividing so that integrating the separate solutions will be a smaller problem than just solving them together.”
Now, understanding the benefits of breaking down large problems into smaller ones is the easy part—and I bet you didn’t need much convincing there.
The real question, which is what we’ll explore next, is how to actually do it.
A million ways to break things down
Let’s start with an example.
Imagine we’re building some sort of SaaS document-sharing application, and we have to implement this notifications widget for it.
At first glance, this might look like a small enough feature that we could probably tackle all at once. But if we look closer, we might find there are lots of opportunities for breaking down the functionality of this component into smaller tasks.
There are, in fact, more than a million ways to break down this feature into smaller chunks. It might take a while to go through each one of them, so instead, let’s explore a few tried and tested strategies that can help us come up with a good breakdown.
1. Ask questions
The first thing I like to do to discover opportunities to break things down is to ask a bunch of questions about the design or feature.
Luckily, since this is a made-up example and I’ve given you zero context about it, it shouldn’t be hard to come up with at least a few questions about this little notifications widget. Here are some of the ones that come to mind for me:
- What do the Accept and Decline buttons do? What happens after someone accepts or declines a notification?
- Does the slightly darker background mean the notifications are unread? Where are we going to store this information?
- Should the notifications show up in real time?
- Is the status badge on the user’s avatar also updated in real-time?
- What happens if a line item has 20 or more tags on it?
- What happens if there are no notifications? Is there an empty state we should show?
- Do any of these components exist in the component library?
And so on. I’m sure you can think of a few additional things to ask about this design as well.
Some of these questions describe the core functionality of the widget, while others are about edge cases. Regardless, each one of them gives us an opportunity to break down this task into smaller chunks.
For instance, we might discover that keeping track of the read/unread state of each notification is not exactly trivial, so it might be a good idea to handle it separately from the core functionality.
2. Make it run, make it right, make it fast
This is one of Kent Beck’s most memorable nuggets of wisdom, which he originally came up with as a way to guide the practice of Test-Driven Development—make the test pass first, and then worry about making the code cleaner.
But even if we don’t practice TDD (or, ahem, don’t write any tests at all), we can still use this strategy to break down a large task into separate phases.
For our notifications widget, the breakdown might look something like this:
- Make it run. Build all of the necessary UI components, hook up any necessary APIs, and write up the business logic.
- Make it right. Polish up the UI, hook up real-time events, and handle any edge cases that you discover during your first iteration, like any loading or empty states.
- Make it fast. Lazy-load any components that aren’t needed until they’re used. Keep an eye on bundle-size, and optimize rendering performance. Do you have any expensive operations that are performed on every re-render? This is the time to cache or memoize them.
3. UI Breakdowns + Component Hierarchies
For tasks that involve a fair share of UI development, it’s a good idea to let the UI do some of the breakdown for us.
React popularized the Thinking in React method for breaking down a visual design into UI components, which is a great place to start—but often not enough. Since everything in React (and most modern frameworks) is technically a component, this type of breakdown doesn’t give us enough information about the most efficient way to tackle a large problem.
So in addition to UI breakdowns, I’ve found it useful to apply some sort of component hierarchy as well. One that I’ve been using for a while now, and that I really like, breaks down the UI into:
- Modules. These are your “pages”, or top-level routes.
- Screens. These are sub-routes, like the content portion of a tabbed interface.
- Features. These are “large components” like a form, a list of users, or your site’s navigation.
- Components. Your classic building blocks, such as buttons, icons, and so on.
For much more about this type of breakdown, check out my previous essay on visualizing frontend architecture.
4. Separate changes in structure from changes in behavior
If we anticipate that the task we’re about to tackle could benefit from some preparatory refactoring, it’s a very good idea to split the refactoring and the new implementation into separate tasks.
This might seem like an obvious thing to do, but when we find a refactoring opportunity while deep in implementation mode, it’s very tempting to handle both the changes in structure and the changes in behavior as part of the same task.
Not splitting these types of changes into separate tasks might actually save us some time during implementation, but would likely result in a slower and less enjoyable code review experience. If you’ve ever had to review a PR that included both a refactor and the implementation of new functionality, you know how hard it can be to make sense of it.
All this talk about decomposition immediately brings up a follow-up question—how do we know when to stop?
There are a few things that I like to consider to know if I’ve sufficiently broken down a piece of work. First, I like to ask myself if I can confidently estimate how long the task would take. The key word here is “confidently.” If I just think that I can get something done in a couple of days, but I’m only 50% sure of my estimate, that could mean the task is still too big to tackle all at once.
The second one is to consider whether breaking something down into two or more pieces would result in considerably more work than doing it all at once. If a task is clearly a 2-line change, then it might not make sense to split it into two 1-liners.
As Jacob Kaplan-Moss says in his great essay on this same topic, breaking things down effectively is a skill, and it takes practice. So don’t be afraid to try things out and follow your intuition. Hopefully, today’s issue gave you a few ideas (and some motivation) for how to tackle your next big project one piece at a time.
ARCHITECTURE SNACKS
Links Worth Checking Out
đź“• READ
- Frontend Masters just published the 2024 edition of the Frontend Developer Handbook—written by Cory Lindley. It’s a fantastic resource to learn about the skills, tools, and technologies necessary to excel as a frontend developer at every level.
- Malte Ubl put together a list of latency numbers every frontend developer should know, which should come in handy for those of you looking to optimize page loads down to the last millisecond.
- The React 19 Beta is now ready to download on npm, and it’s full of goodies—new hooks, new APIs, and a bunch of improvements to make our jobs at least a tiny bit more enjoyable (so long, forwardRef!)
- Alex Kondov published an in-depth essay on how to style React applications, which is part of his upcoming software design book, The Full-Stack Tao (which I’m really looking forward to, btw.)
​
🎥 WATCH
​3x Explore, Expand, Extract by Kent Beck
You thought I was done referencing Kent Beck in today’s newsletter, didn’t you? Well, not yet—I have one last piece of Kent Beck's wisdom to share with you today. In this talk at the YOW! 2018 conference, Kent talks about his experience as a lead engineer at Facebook and how the “move fast and break things” culture of the day influenced his Explore, Expand, Extract framework, which focuses on trying a lot of low-risk experiments and doubling down on what works.
​
🎧 LISTEN
​Putting React In The Browser​
I really enjoyed this discussion between Jake and Surma on the Off the Main Thread Podcast, answering a question I feel we should have asked a while ago—what would it take to put React (or, more precisely, DOM diffing and updating functionality) directly in the browser?
That’s all for today, friends! Thank you for making it all the way to the end. If you enjoyed the newsletter, it would mean the world to me if you’d share it with your friends and coworkers. (And if you didn't enjoy it, why not share it with an enemy?)
Did someone forward this to you? First of all, tell them how awesome they are, and then consider subscribing to the newsletter to get the next issue right in your inbox.
I read and reply to all of your comments. Feel free to reach out on Twitter or reply to this email directly with any feedback or questions.
Have a great week đź‘‹
– Maxi