Startup engineering teams face many decisions about what to build. At Close.io, many areas compete for the focus of our small engineering team. Customers often have one little thing they really need. Our team envisions the next big thing to move the product forward. There are poor UX workflows to optimize. We have an idea on how to grow our customer base faster. And of course there are always bugs to fix. The list is endless!
A small engineering team doesn’t have the time or resources to regularly improve every part of a product. It’s not uncommon for a section of an app, once launched, to remain untouched for a year or longer. We usually work to solve a problem or empower customers in a new way or fix a pain point, and then we move on to something else.
One reason I believe our super small team at Close.io has been successful is that we often solve multiple problems at once. When a feature needs to be built, we often expand the scope a bit to include other related problems or features that naturally go together with the first one.
Another way to phrase this idea is: rather than solving a problem, solve an entire class of problems.
While this advice may sound obvious, there’s enormous pressure to finish a project as quickly as possible. There’s always the next important feature, bug fix, or redesign from the roadmap to move on to. Shipping even the smallest version of a feature on time can be difficult enough already, since software schedule estimating is never easy. But there’s great value in not just shipping a feature or fix in the smallest form possible.
Application: Fixing Bugs
Let’s start with a simple real-world application: you notice a bug that needs fixing. After some investigation you figure out what the problem is and how to fix it. You fix it and maybe even add a unit test for this case. Time to move on, right?
NO! If you stop there, you’re making a crucial mistake.
At a minimum, you should try to figure out if the same bug exists anywhere else in the codebase. Often a single ack search is enough to find the same bug in many places. Next, consider if there might be other conceptually similar versions of this bug elsewhere. Ideally you’d also follow The Five Whys and discover how this bug got introduced, how it got past code review and QA, etc.
Again, this advice may seem obvious, but consider someone’s natural instinct when a user complains. These complaints usually come in the form of a vague problem (e.g. “it won’t let me change my email address”). First you figure out what the real bug is (e.g. “The form for changing email addresses doesn’t show a confirmation message”). Bugs usually come in very specific forms like this. It’s not uncommon for a programmer to simply fix the bug and move on. But it’s important to stop and consider if similar bugs may exist elsewhere (e.g. “how form confirmation messages work throughout every part of the app”).
It’s the sign of a mature programmer to ask “why” and consider preventing future bugs of a similar type.
Example: A broken URL on Close.io
I recently noticed a problem with a specific URL on our site was not working. I discovered the cause was that two Python view functions in our Flask app shared the same name, which just silently breaks one of them. My first instinct was to rename the broken view with a unique name, and move on.
But I remembered it wasn’t the first time this had happened and I recognized it likely wouldn’t be the last, so I thought about the problem more broadly. I knew a syntax issue like this should be detectable, so I spent some time setting up pylint and reviewing its results. Pylint uncovered another case of the same error, as well as other types of logical errors elsewhere, which I fixed. Finally, I added pylint to our continuous integration system to automatically detect any Python syntax issues in the future.
So rather than fixing the broken URL, I fixed all cases where URLs were broken for the same reason. I also found and fixed other unrelated instances of “detectable” syntax issues. And I also automated this process so that this entire class of issues can never happen again. Do you see how much more powerful this type of fixing can be?
Once you’ve discovered the specific causes of a bug, there’s no better time to find and fix other similar bugs. Even when the roadmap begs you to move on, the benefits of squashing related problems are even stronger:
- It’s good practice to fix bugs before writing other code (#5 in The Joel Test)
- If you can discover and fix bugs before more users experience and report them, you’re preventing user pain.
- You’ve already done the hard part of figuring out the specifics of the problem. If you don’t completely resolve it, you’re forcing a teammate or your future self to have to waste time relearning the same thing!
Don’t just fix bugs. Fix an entire class of bugs.
Application: Designing Features
The temptation to solve a single problem at once is even larger when it comes to features. Features are often a response for solving a user’s pain point, or an idea designed to empower your users in a new way. Naturally, the team is excited to ship as soon as possible.
Furthermore, a good product designer will optimize a feature to be as simple as possible for the specific workflow it’s designed for.
The problem is that over time, rather than designing one cohesive experience, you’ve glued a bunch of individual features together. If you’re only thinking about solving one specific problem at a time, you’re missing the bigger picture of how everything will fit together.
Software written in this way turns out to be super complex because hundreds of small problems were solved separately rather than a few big problems being solved elegantly.
Don’t design a single feature; always be designing for the bigger picture.
Example 1: Close.io Search & Filtering
An example where I think our team nailed this early was with search and filtering. From ElasticSales we knew that salespeople would want to slice and dice their leads in a million ways.
When starting Close.io it would have been understandable if we solved this initially by just slapping a couple of the most commonly requested filter options, like “Lead Status”.
However we knew that this was a narrow and short-term solution. It wouldn’t be enough to last and wouldn’t be enough to “wow” people. Quickly, power users would outgrow our simple filters and we would be forced to keep adding additional one-off filters and complexity. We’d have to keep redesigning as the number of filters grew and redesigning again for each new idea like exclusion filters or nested “OR” conditions. We would have started fast but slowed very quickly.
Instead, we designed a framework to solve the larger problem. We invented a search language and then UI to allow filtering by a very large number of useful sales attributes and combine them together with boolean and/or/not keywords. It took longer to do it this way than just adding a couple basic filters. But we established a paradigm of how searching and filtering worked in Close.io that has powered innumerable use cases our customers needed and has lasted 2+ years. Our customers rave about its power, and PandoDaily wrote about it.
I’m definitely not saying we had to build this feature to 100% completion from day 1 (and we didn’t – we still iterate on it today – and in many ways it’s very far from complete). But thinking through a scalable solution for this problem rather than slapping on a few quick filters has given us a big advantage. Having an end goal in mind allowed us build a version 1 that didn’t have to be thrown away when we built v2 and v3. We have ideas for what an amazing version 5 and 10 may look like, and we won’t have to start over – all because we planned ahead to solve search & filtering more broadly.
Example 2: Close.io Reporting
Some of our competitors have dozens of individual “reports”. They tack on a new report every few weeks because users always want more reporting. Close.io was really far behind in reporting but the thought of adding dozens of reports made us want to cry. So instead we built one super powerful charting tool (Explorer) that, in one fell swoop, allows you to visualize almost any attribute of your teams’s sales activity.
Example 3: Close.io Bulk Actions
We needed to build a way for users to “bulk delete” all their leads. Rather than building this alone, we designed a system that would work for not only Bulk Delete but also Bulk Edit and Bulk Email (two other features we knew we wanted to build). Because of designing for this, we were able to later launch the additional two features within a very short period of time. Coding the two additional features became much simpler and the UX for all bulk actions was considered together rather than tacked on without cohesion.
Architect your product to solve an entire class of problems at once.
If you don’t, you’ll end up with software that’s missing important features and users will quickly outgrow the one thing you helped them with. Or you’ll keep tacking on additions in a non-cohesive way which makes a complex product over time.
Said another way: it’s easier to end up with both successful users and a cohesive UI & UX if you solve and design for a few big problems rather than a bunch of individual little ones.
Technical debt can clearly become a big problem and slow development. But it almost never feels worth rewriting something just for the sake of code quality. The benefit of doing projects solely to “pay back” technical debt is hard to justify.
The best time to solve technical debt, refactor code, etc. is in the midst of making other changes to that part of the system. When you’re working on an improvement involving a problematic part of the codebase and you’re considering making bad code even worse… go ahead and take the extra time to refactor and improve it. There’s no better time to do so, since you’re already having to grok how it works and carefully test those parts related to your improvement.
When redesigning how one part of your product works, consider how the rest of your product works. It may be easier to solve multiple problems that relate to each other all at once.
Example: Close.io Onboarding Process & Email Setup
We wanted to introduce a set of onboarding steps for new Close.io users. One step would be an easier way to connect your email account (for our 2-way email syncing to work) rather than having users do so later in Settings. What we did is build and launch a few features all at once:
- Onboarding steps for new users
- Simplify getting email account credentials by:
- Auto-detecting your email service, IMAP/SMTP hostname, port, etc. when possible
- Consolidating setup of incoming & outgoing email settings into one step
- Use OAuth instead of passwords when a Gmail / Google Apps account is detected
- Support for multiple email accounts & identities per user
Not all of these features were crucial for the main priority at the time, which was to improve onboarding and make it easier to setup email. But they made a lot of sense to build together, since they were interrelated. We would have had to redesign, recode, and retest the Email Settings page regardless so it was the perfect time to design it to support setting up multiple accounts.
Supporting multiple accounts is a valuable feature that we always planned on building. But if we hadn’t built it alongside these other features, it likely wouldn’t have become a big enough priority to get built on its own for quite some time. By building to solve multiple problems at once, we were able to do more, faster, than had we been trying to solve independent problems in serial.
Risks & Rewards
You may now be thinking, “Isn’t this scope creep, and isn’t scope creep a bad thing?”
Indeed, if you keep expanding the scope of your projects to solve more and more problems you will never ship or meet deadlines.
But I’m actually advocating for more planning. More deliberateness in your design decisions and planning. More hesitation before starting projects that only solve only one problem. Design your product with end goals in mind. Design code and processes with your future team in mind.
Expand a project scope opportunistically where it makes sense. Reschedule items onto the roadmap sooner if they are easier to build alongside whatever your current priority is. Often you won’t return to a problem for many months or even years. So if you can put an entire set of problems to rest all at once, do it even if it takes a bit longer.
The principles I’ve been talking about should help you make a much better product over time. When you solve one problem, it’s not that much harder to solve a bigger class of the problem.
The way to keep from turning this advice into scope creep is to slow down. Not slow down in the sense that your team & product shouldn’t be moving quickly. But slow down in the sense that you should do less, but better. Do fewer things, but more that have longterm impact. You can’t do this for everything, but try to do it for the important parts.
So the next time you design a feature, fix a bug, or otherwise try to improve your product, ask yourself, “Can I solve multiple problems at once?”