Over the years at Slack — usually during periods of high growth — teams have defaulted to working in a waterfall where an idea is proposed and researched, then designed or spec’d, then built, in that order. It’s not the most efficient way to work and as a company grows — with more people involved at each step — it only gets slower.
There is an incredibly high cost to going backwards; if you realize you got it wrong during the build phase, going back to the drawing board can be seen as a departure or a failure to plan adequately. Hindsight is 20/20, as they say, so why wait until the end of the project to benefit from that clarity? Escaping this mode in order to work iteratively requires a high degree of trust and autonomy that takes time for teams to build, so it helps to talk explicitly about process. We’ve found success by focusing on the prototyping process as a way to break out of outdated, linear waterfall thinking. What we’ll share here is not a prescriptive methodology but rather a simple framework that can be adapted to a team’s needs.
I’ll be borrowing some terms and ideas from the Design Thinking process championed by Tim Brown at IDEO and written up succinctly on interaction-design.org, so if this post resonates with you then the Design Thinking literature has a lot more to offer!
Prototyping is a process for learning and evaluating. If you are at a point in a project where you have an assumption you’d like to validate, a specific direction you’d like to get feedback on, or a technical plan you want to evaluate, prototyping can give you the information necessary to make a firm decision.
Prototyping can also be used to evaluate broad opportunities before investing the full resources of a team into them. Building a rough version of that feature or system you’ve been kicking around for a year gives you the chance to decide not to pursue it. This is a skill that enables product and engineering teams to assist with critical business decisions.
Most importantly, prototyping gives us the guardrails to fail safely and productively. Failure is an important part of the learning process and is just as valuable as success.
There are three steps in the prototyping process: hypothesize, execute, and evaluate.
1. Come up with a hypothesis and define success and failure
Your hypothesis is the statement that the prototype should prove or disprove. It’s important to define what success looks like up front — especially when evaluating subjective design directions — but equally important is deciding what failure looks like. Remember that failure is not something to be avoided here, it’s simply one of the possible outcomes to plan for. A failure will almost always lead to a reformation of the problem and a new prototype; this is the relationship between the ideation and prototyping phases of the design process.
Let’s look at some example hypotheses we had at Slack and the success/failure criteria that might accompany them.
💡 What-you-see-is-what-you-get messages can be displayed seamlessly alongside existing messages in the database.
✅ Older messages continue to display correctly alongside newer WYSIWYG-formatted messages.
✅ The search infrastructure can be modified to return results from both formats.
❌ All old messages need to be migrated to a new format to support WYSIWYG.
Result: Success. This was one of the early prototypes that paved the way for the large WYSIWYG engineering project.
💡 CSS-in-JS will make it easier and lower-risk to implement dark mode.
✅ We put together a basic dark mode prototype using a CSS-in-JS solution.
❌ The CSS-in-JS migration path causes us to fork our styles into two places that don’t stay coordinated without additional tooling.
❌ CSS-in-JS doesn’t unlock new technical possibilities for us.
Result: Failure. The migration path using CSS-in-JS didn’t seem worth the investment at that point in time and we instead opted to use CSS variables, which you can read more about in a previous blog post.
💡 We could make Slack render in under 1 second by serving static HTML from the CDN rather than rendering a template from the server.
✅ Our proof-of-concept is close to, or under, 1 second of load time.
❌ We hit an insurmountable technical snag.
❌ We can’t come convincingly close to 1 second.
Result: Success. This was the original prototype that led us to rearchitect the Slack client. You can read more about that project in this blog post.
It’s important to write these down before you start. The goalposts are easy to move as the prototype comes together, especially when it’s exciting.
2. Write some throwaway code
Prototyping is chance to validate an idea without all of the baggage that comes with production-quality engineering. If you’re planning to throw the code away at the end, then shortcuts like hardcoding data, skipping the tests, ignoring the linter, etc. are all fair game.
Working in a separate repo or isolating the prototype to its own page can help mitigate safety concerns and increase code readability if writing production code is necessary. Always take the time to plan your technical approach; safety is essential!
3. Evaluate, document, & make a decision
The takeaway is not the code or the output itself, but the learning that comes out of it. Take the time to evaluate and document the work and decide what you’ll do with this new information. What went well and what didn’t? Were there any surprises? Did you meet your success criteria? Were any of your assumptions incorrect?
Documenting the lessons learned is not just a useful way to recap the work — it can become an important historical artifact down the road when another team is considering similar ideas. The ability to say “we tried that; here’s what we found” gives others valuable signal to work with.
If your prototype failed, the next step might be to formulate a new hypothesis and try a different approach. If the prototype was successful and demonstrated the viability you were looking for, perhaps you capture what you learned in a detailed technical spec that serves as the basis for an engineering project.
Putting it into practice
Reading through this definition you might be struck by how formal and rigid it all sounds. Reality is always a little murkier — the stakeholders, goals, and personalities of each project are unique and can dictate aspects of our approach. It’s important to be aspirational in the definition of our process, though, so that we can evaluate how we’re doing over time and find ways to push for excellence. In that spirit, here’s are some anecdotes about how we prototyped our way through the recent redesign of Slack’s desktop client.
Suspending disbelief: at the start of the project we had roughly the same number of designers and frontend engineers, so we paired up every day. We’d come up with the next iteration of a concept we wanted to try — for example, what if Slack only had a “start menu” from which all controls were accessible? — and spend the day building it, having decided on some criteria and scope. Our development environments allow us to build and share frontend code with others, so at the end of each day we’d link the team to a build and ask for feedback. For these prototypes we’d hack and slash our way through existing code, deleting and rewriting anything necessary to communicate the concept. It was messy and fun and it required that the team have a high degree of trust and an ability to suspend disbelief; evaluating prototypes is hard if you don’t communicate the success and failure criteria, and sometimes it requires that you ask the evaluator to use their imagination to fill in the gaps.
Prototyping for an audience: as our concepts got further along we reached a point where we needed to widen the audience. This required that we build several variations into the codebase with the ability to switch between them. The fidelity of these prototypes was higher — meant for a general internal audience with less of a tolerance for bugs and missing pieces — but we continued to ask for specific feedback based on the success and failure criteria we outlined for each variation. We spent time up front building in the guardrails to ensure that this code was clearly marked as prototype code and that it was safe to check into the codebase.
Dialing it in: even as we wrote the final production version of our designs, we took the time to iterate on smaller and smaller details. We’d finish a piece and ask, what if this was centered instead? What if it used a color from the user’s theme? What if we built this the other way? Keeping this iterative spirit alive helped us stay connected to the larger goals we were working towards.
Our use of prototyping is still ramping up at Slack. The challenge of communicating this process alone has kept us busy, but to that end we’ve added it to a set of company-wide product principles and are encouraging teams to find more ways to iterate. If this sounds like the style of work you’re looking for, join us!