Congratulations, you are now proficient with both algorithms and behavioral questions and are at the final step: System design questions. System design questions are scary and tricky, because they have no right answer, similar to behavioral questions. This is why these questions are crucial for passing your software engineering interview: Their ambiguity is what makes them an effective tool to gauge the technical strength of any experienced engineer. Unfortunately, there’s no concrete process to really master this type of question, but here’s the general approach you should have going into them:
- Your goal is to build a large and complex system at a high level
- The system you build should be good
- You should have solid reasoning for why your system is actually good
To clarify these ideas (like what exactly a “good” system is), I have the following general advice you should follow for these questions.
If You’re Coding, You’re Failing
As mentioned in idea #1, your goal is to build the system at a high level. This is the most common way I see engineers fail these problems. The entire point of these questions is to see if you can do more than just write code within your tiny corner of some massive universe. So if you’re not coding, then what should you be doing? At a broad level, you should be describing the components necessary to make the system work. From there, you can break down how the components function (and maybe write some pseudo-code at the most) and explain how they work with one another. Here’s an example run-through for a question a mobile engineer like me would get - “How would you build the Instagram main feed?”:
- Rendering layer - Showing the actual list of items to the user. Some topics here are what component to use to actually render the list (RecyclerView), what business logic dictates each item’s appearance (adapter), view recycling (View Holder), and updating UI after interactions such as liking and sharing (redrawing APIs and model changes).
- API - In the end, all fresh content needs to be fetched from the server. Some topics to discuss here are how we would make those API calls (Retrofit), request timeouts, and callback setup.
- Local persistence - To allow the user to always have content to browse after the initial content fetch, we can persist the content locally (which Instagram and a lot of other major apps do). Some topics to discuss here are the choice of persistence (SQLite, Realm, etc), keeping data in-sync between the client and server, and fetching latency.
When it comes to envisioning inter-component collaboration, one principle that has helped me is to “think in terms of interfaces”. Don’t focus on how components work underneath; this is the easiest way to start coding and fall into a trap that’s hard to get out of. Think about the contract in between pieces: What does A need from B and what does B need from A? Once you flesh this out, all of the implementation will naturally fall into place. To conclude this section on a very simple note: If you are drawing boxes with arrows in between them, you are probably on the right track.
Clarify The Requirements
Similar to algorithms and data structures questions, clarifying the requirements of system design questions is absolutely crucial. What the requirements are will heavily dictate what your design ends up being. Here are some questions you can ask to do this:
- What features need to be supported by the MVP (Minimum Viable Product)?
- Who are the users of the product?
- What does the flow look like for the standard use of the product?
- What devices will the product be used on?
The answers to each of these questions will help inform your architecture. Paring down the feature list for the MVP will prevent you from wasting time designing components you don’t need. Learning that you need to support users in developing markets means you need to care more about performance metrics such as latency and app size.
Prepare For The Worst
A good system is a robust system. Make sure that whatever design you come up with is as bulletproof as possible. How does your system handle errors? How does it prevent hacking? Can it function in poor network environments? These are all things good engineers consider, and the more of these you account for, the more seniority you show when answering these questions. The mere building of systems is often glamorized, especially in today’s startup-driven atmosphere. Yes, furiously hacking something from 0 to 1 and just getting it to work is awesome, but you can only get so far as an engineer just getting stuff off the ground. What separates the good engineers from the merely decent ones are that they know how to build something that lasts, not just something that works. You need to keep this in mind when answering these questions.
Think Into The Future
A good system is also one that scales. The features you want to add in the future should heavily dictate your current design. At a higher level, you want to make sure that you make as few assumptions as possible as you’re building out components. I’m sure we all know the pain of dealing with code that just crashes when some specific condition isn’t met. In a similar vein, you want to make sure that your system is easily extendible. For example, if you are building a platform that initially only supports short-form video up to 15 seconds, you might not want to force a complete rewrite when you want to support 30 second video. To facilitate all of this at a more meta-level, you should also think about what logging you will need to understand your product’s usage so you can properly evolve it.
So how do we figure out what the future holds? Well, as mentioned before, you can always ask the interviewer. Another way (which is more impressive) is to put on your product manager hat and come up with this product direction yourself. For system design interviews, this will rarely be rocket science. The vast majority of the time, you will be asked to build a basic version of a major product or feature that already exists. If you have a basic knowledge of what the top software companies are doing nowadays, you will already know what features are reasonable to extend the initial version with.
Show Your Work
Another thing system design questions do is see if you are able to put pieces together. A weak software engineer is someone who tries to turn their work into routine. They develop just enough of an understanding of the system to perform small to medium sized tasks within them. They never challenge the current way of doing things and just accept their environment as “the way things are done”. A strong engineer always has a deep understanding of their environment and is constantly trying to challenge and improve it. They draw upon industry knowledge and historical context to explain their technical decisions and have strong guiding principles. These are the behaviors you need to demonstrate to ace a system design question.
For example, if you’re using RecyclerView for the aforementioned Instagram main feed problem, you shouldn’t only justify that decision with, “It’s the newest list rendering component and suggested by Google.” You should talk about its cleaner design pattern vs. ListView, forcing view recycling to optimize performance. You should talk about its selective redrawing APIs like notifyItemAdded() while ListView can only redraw the entire list. You should talk about how RecyclerView can be heavily customized with more powerful adapters while rendering something like a grid with a ListView isn’t really feasible without massive hacks.
The goal of this article is to share some high-level tips around system design interviews. If you really want to go deep, check out these related resources: