Welcome to The Coding Interview. You Suck
This is my rant/vulgar opinionated guide about coding interviews that will instruct you on how to prepare for them, the mental models you should use to improve at them and become a better engineer, and, most importantly, why people just suck at them.
I will also explain how to not suck at them, the methodology I used to become competent (henceforth known as the incredible concept of “git gud”) at interviews, and how to approach interview questions in a way that makes sense to you.
This guide will not cover resumes or how to start the interview process, but I might write a separate guide for that if there is interest.
I will assume you know the basics of how coding interviews are done (phone screen, on-site, etc.) and what the overall process entails. I’m mostly going to talk about the nature of coding interviews, what is being tested, how to study for them, and how to consistently pass.
I will update this guide as needed.
Disclaimer: Everything said here is my opinion (somewhat) exaggerated for comedic effect. It does not reflect how I conduct myself professionally. I am simply writing this for entertainment and informative purposes only.
I do not make money off of any of the resources I recommend (other than my own services).
To everyone else reading this who isn’t trying to hire me, yes I know I’m an abrasive butthole. Please stop emailing me. Pls don’t fire me kthx.
Due to numerous requests and a high amount of interest, I coach as well.
Book here:
https://www.echantech.com/book-online
Blog (for other interesting content):
https://www.echantech.com/blog
You can watch prior coaching sessions for free on my YouTube channel:
https://www.youtube.com/playlist?list=PLTh5zOK8tL21kxJ67rDRo-EARDDy8t86V
And if you want to support my work and tip me, here’s my Ko-Fi
https://ko-fi.com/echantech
Quick Links
Link to the guide (if you want to share)
https://tinyurl.com/4hdj47mv
Adding note that System Design guide is under construction
Added Mistake #9: Being full of shit. Because some of y’all are so full of it.
Version 1.0 is now deprecated. It can be found here.
Intro: You Suck At Coding Interviews
Who Are You? Why Should I Trust You?
Mistake 1: Not Putting Enough Time
Mistake 2: Interview Approach Is Inconsistent
Mistake 3: Your Interview Practicing Is Inconsistent
Mistake 4: Not Fixing Your Own Mistakes
Mistake 5: Not seeing how one decision or mistake leads to another
Mistake 6: Not Understanding Your Own Solution Before Coding
Mistake 8: Not Being Fast Enough
What Makes A Good Study Source?
Data Structures And Algorithms (DS&A)
1) Understand an algorithm/data structure to autonomous proficiency
2) Derive rules/hints of when to use them
3) Backtest those preconditions
4) Understand what happens when you improperly apply the algorithm
1) Can a Five-Year-Old Understand?
2) Optimal Solution Vs. Your Solution
How To Solve The DS&A Problems
Why The Fuck Are We Asking These Questions?
How To Study (Grokking the System Design + DDIA)
How To Solve The System Design Problems
Example Modern/Product System Design
How To Prepare For The Behavioral Interview
How do you respond when you disagree with a coworker?
“Do You Have Any Questions For Me?”
What Is the Interviewer Looking For?
But Doesn’t The Interviewer Already Decide In the First Minute If I Pass Or Fail?
What Is The Interviewer NOT Looking For? Red Flags
Take Melatonin The Night Before
Always Take The Bathroom Break
It’s been two years since I released my first version of this guide and, during that time, I realized that there are a lot of things that went over people’s heads—especially the raw mechanical parts about improvement and the esoteric explanation of the process. This version will hopefully be more streamlined and explain step-by-step what you need to do on an easily understandable and more basic level.
The underlying belief system is the same: you need to git gud. You need to perform well in rain or shine and in sickness or health. The easiest way to get an offer you want is to deserve one.
If you’re reading this, chances are you’re in the middle of preparing for a coding interview, you suck at them, and you’re looking for some quick tips to help you do well in two weeks or less.
If that’s you, you might as well stop here because this is not what this guide is at all.
This guide is an in-depth exploration on how to beat any coding interview, land any job offer at any time, and become a better engineer in the process.
The process is not easy. However, faithfully implementing what is in this guide will be the shortest way to achieve consistent and high-value offers. If you do this right, you need only spend a hundred hours or so. And I don’t mean a hundred hours while you have Netflix on in the background. I mean a hundred hours of crying, rage, and imposter syndrome.
The benefit of doing this right is that once you master the proper thinking, you will never have to study again. You will also become a much better engineer.
Today, it is easier than ever before to pass the coding interview. The reason is that the interview process is very well understood, mostly uniform, and a predictable exam. The range of topics are broad, but most of the questions you encounter are similar across companies. A lot of tricky and odd questions that were once commonplace are banned. No one company is known for using a certain type of question or favors one topic exclusively. Microsoft brain teasers from the 2000’s are a silly relic of interviews’ past. Heck, even 4 years ago, the questions and topics were different.
For my very first job, I was asked to actually write the code for the producer/consumer multithreading problem as a Java developer. The closest I’ve done to multithreading prior to this was taking a computer architecture class. I had never before written formal multithreaded code with a semaphore or a lock. These days, I think it’s very unlikely to ask this to a L3 candidate. At best, you’ll probably be asked to do a conceptual rundown.
Yet, while studying for my own interviews, I found I was inconsistently performing despite the wealth of resources at my disposal. I probably did over 100 problems and wasted a shit ton of hours before realizing I was not getting any better at all. I had to rethink my approach to these questions. This guide is just a culmination of all the efforts I undertook to consistently land interview offers.
I’m writing this guide to help you save a good couple hundred hours of your life. And your sanity.
So, are you ready to git gud?
I got my first job at Apple after getting my bachelor’s degree. I started out at the bottom of the rung, working with people who were 10 years older than me with master’s degrees.
Less than four years later, I was the primary stakeholder in senior-level work for refactoring our project’s architecture and the sole engineer working on that refactor.
At Apple, I was also active in the search for adding both junior and senior candidates to our team. Yes, I actually interviewed people for positions above me. I failed several of them outright. Even though decisions required a team majority vote, the result tended to be that if I did not give a candidate a pass, he was not hired.
When I applied for other positions four years after starting Apple, I aced all my phone screens and hit a 80% rate for on-site interviews. Currently, I’m one of the youngest engineers on my team at Uber for Infrastructure, managing the developer experience of hundreds of mobile engineers.
Not only that, I reproduced this success at a higher level with my clients. Want specifics? Here are all my long-term clients’ results from 2021 (direct emails and contact information upon request. I’d like to make sure that they are comfortable with being contacted).
I don’t know how people can say I don’t know what I’m talking about or cry that the interviews are unfair if I can achieve those kinds of extraordinary results consistently.
One of my clients was handicapped by a deadly disease that shut down the world economy and he STILL aced every interview. You complaining about interviews is the equivalent of crying that a marathon is unfair while you’re getting lapped by multiple guys who broke both their legs at the same starting line.
Either you would need to accept that me and my clients are lucky every time and you are perfectly fine. Or that I am extraordinarily skilled and you suck. Maybe both.
Bottom line: I get results consistently. You’re allowed to disagree with me once you can get those results for yourself and others as well.
Still don’t believe me? Feel free to tune in when I am streaming. You can even follow my clients’ progress in real time.
And I dare you to find someone who can coach clients to get a 70% success rate on all their interviews. Not a 70% chance of getting at least one offer, but a 70% chance of acing ANY INTERVIEW across ALL CLIENTS and ALL COMPANIES. Especially if its mostly FAANGM tech companies.
Why should you indeed? After all, you’re only going to do the interview once every 2-3 years. Why should you invest almost 100 hours into a single interview prep and basically never see the light of day for a month or two? Why not just game the interview and study the bare minimum to get the job you want? Why not just get into a company and work up the ladder? Maybe you just suck at tests and are better at practical stuff.
Firstly, it increases your negotiating power when it comes to the job. The more offers you get, the more you can convince the company to give you more money. The amount can be anywhere from 20k to 50k, depending on the number. You can use levels.fyi to do the comparison but the range between the ends and the mid for an L3 position is about 35-40k. It takes you about two cycles for you to be calibrated back to your true level. Usually, these meetings are every 6 months so this can mean that your pay will come back to your actual skill level in a year at the earliest. Those 100 hours of study effectively works out to about $350-400/hour. What other job could you do that pays you that much?
Secondly, it is not that difficult to pass these interviews. Google tends to be conservative so you’re looking at a 10-20% pass rate at worst once you go on-site. That’s like getting a B in school or getting into UC Berkeley/UCLA. Chances are if you can pass the interview at one company, you can pass at almost any company.
Finally, working up the ladder is arguably much harder. There are people who have many, many more years of experience on you who will be first in line for promotions even if you do a stellar job. Everyone will be scrambling to take high impact projects or taking credit for things. You’ll be playing a much different game than simply being compensated for your skill. If you’re a social and personable type of guy, then maybe going up the ladder is for you. I am not.
But if you’re someone who likes to be rewarded for their raw skill and dedication, the interview is just that: an exam of skill. Unfortunately, it's not about how good you are as a professional programmer. Rather, it is an exam of how competent you can get at a game that has its own rules/meta and tactics that resemble real world coding. You very rarely get to code up an algorithm from the ground up and you aren't going to design all of Twitter in an hour. But like any game, you can get extremely good at it and some of the core principles do transfer over.
Randomness and luck no longer play a role. It is entirely on you and how good you are. No bullshit. Not excuses. Now isn’t that an extremely freeing thought?
Let’s start by figuring out hypothetically where you would rank in terms of algorithm expertise.
For all intents and purposes, we will use the Google Leveling System to refer to engineer levels with the approximate definition:
Intern, still in school, trying out for Google Summer of Code
L3, fresh out of school, junior engineer, average 170k TC
L4, few years of experience, solid industry engineer, average 260k TC
L5, around 5+ years, senior engineer, tech-lead capable, average 360k TC
Try the two following questions. These are on the easier side of questions.
When I say you need to be fast, I mean it. You need to be VERY fast. Not “Oh, I could’ve gotten to the right solution with a little more time.” No. You need to be “I got to the optimal solution with five minutes to spare” fast. And maybe even faster than that.
With CS being ultra-competitive these days, you’re going to need more than simply spitballing random information to try to get an optimal solution. Time is of the essence during these interviews and every second is incredibly important.
The only way to get fast is to practice. Practice slowly and deliberately. The speed will come.
Solve this one in 30 minutes.
/**
* Serialize and deserialize a general (n-ary) Tree
* A
* / | \
* B C D
* / \ / | \ \
* E F I G H J
* |
* K
*/
A has children B,C,D
B has children E, F
F has child K
D has children I,G,H,J
C has no children
Solution:
// Encodes a tree to a single string.
public static String serialize(Node root) {
String s = "";
if (root == null) {
return "";
}
//visit the node
s += root.val;
//if there are any children
if (root.getChildSize() != 0) {
s += "(";
//itterate through all children
for (int i = 0; i < root.getChildSize(); i++) {
String child = serialize(root.getChildAt(i));
s += child;
//Remember to not add the last comma!
if (i != root.getChildSize() - 1) {
s += ",";
}
}
s += ")";
}
return s;
}
public static Node deserialize(String s) {
Stack<Node> stack = new Stack<>();
Node currNode = new Node("");
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isLetter(c)) {
//append to string
currNode.val += c;
} else if (c == '(') {
//commit the str to stack as a node
stack.push(currNode);
currNode = new Node("");
} else if (c == ',') {
//append this node as a child to the top of the stack
stack.peek().children.add(currNode);
currNode = new Node("");
} else if (c == ')') {
//append to the top of the stack, pop it, and then retain the node
stack.peek().children.add(currNode);
currNode = stack.pop();
} else {
throw new RuntimeException("MALFORMED string!");
}
}
return currNode;
}
Intern/L3: Deserialization and serialization (deserialization has to be mostly there, maybe a bug but the idea behind it should be correct).
L4/L5+: Fully working for both. L5 would be more likely to write out something similar to the above because it is cleaner/simpler (also smaller string size) than the alternative of writing out the number of children and inserting a delimiter between the values of interest.
For instance, what happens if you have 10 children at each level and you decided to include the number of children in your serialization? Or what if your tree representation has millions of children? Your string length would essentially double because you need to carry around the number of the children whereas a bracket representation would be more compact and consistent and scale with the number of children.
Solve this in 45 minutes.
Consider your smartphone. A view is something on the screen that is rectangularly bound that has a x and y coordinate with a length and width. Views can be children to other views. For instance, in a list, the list view will have item views as its children.
Any view on the screen can overlap any other view and any touch event is intercepted in Z order. The view that is overlayed on top will be given a chance to intercept the touch event and consume it. If it does not consume the event, then it is passed off to the view underneath the view, and so on.
Solution:
The path that would be constructed for the most frequent sequence of views can be done from a trie. Why a trie over a tree?
Consider the path 1,2,3,4 and 1,2,3,5. Both paths share 1,2,3 and we can efficiently store these values as a trie as if it were a common substring. Technically, a trie is a sort of tree but we’re just splitting hairs if we want to argue the definition. But hopefully, you get the idea.
public class Node {
final int viewId;
final int frequency;
//this is the frequency of access from root to node. That is, a frequency of 4 at viewId 3 would mean the frequency of access of 0-1-2-3 is 4
public List<Node> children = new ArrayList<>();
public Node(int viewId, int frequency) {
this.viewId = viewId;
this.frequency = frequency;
}
}
public void dfs(Node node, List<Node> pathThusfar) {
List<Node> neighbours = node.children;
for (int i = 0; i < neighbours.size(); i++) {
Node n = neighbours.get(i);
if (n.frequency > maxFrequency) {
maxResult = new ArrayList<>(pathThusfar);
maxResult.add(n);
maxFrequency = n.frequency;
}
pathThusfar.remove(n);
}
}
Return value is maxResult and maxFrequency
Intern: Would not ask the first part. I’d probably just give him the logging implementation and ask him to write a trie search for the most commonly occurring path.
All levels: Should be able to solve c fairly quickly.
L3: Some consideration of the first part of the question. May not mention the singleton pattern and may keep creating multiple logger instances with a builder-like solution (maybe creating a logger that only logs certain events based on hard-coded configurations). Expect the second question to be fully implemented and working.
L4/L5: Should absolutely nail this one. I don’t think there is a more optimal solution but email me if you can think of one. Would be concerned if did not mention the singleton pattern. These are just simple BFS and DFS.
L5 should also mention how to make the logger more extensible without an interviewer asking him to discuss this possibility.
Design Yelp/FourSquare Check in System (L4+)
Solution:
See the attached screenshot. Your answer should come very close. If not, the alternative should be well-reasoned.
L4: Should be able to glue a basic end-to-end system without much consideration for the tradeoffs. Approach may not be systematic, but the answer should be close to the proposed one.
L5: Should be able to systematically approach, define, and derive the problem and solution. Every step is logical, self-contained, and easily understood. Should be closed to the below solution. If not, then you should be able to reason how the answer was derived, why it is right/wrong, and how your solution is better and why.
L5+: Should be able to talk about tradeoffs and scalability and pick the appropriate data store and technical components. Should cover all of what L5 does. Should also immediately be able to talk about developer productivity and how they will modify, maintain, and use the system and how the organization accommodates for that.
The industry has taken great strides to eliminate the “stupid” questions in the past 4-8 years and much of my commentary in the previous guide is somewhat moot.
But the interviews still suck, just a whole lot less and for different reasons. Fortunately, it's almost to the point where the suckage of the interview is almost negligible compared to the skill of the engineer himself because a competent engineer has a better chance now more than ever to ace the interview.
This is something that should be addressed.
There are two disconnections in the interviewing process today. The first is that there is a disconnect between what the average engineer/student does and a top tier engineer. And this wisdom is obscured behind a vast amount of “information” that is perpetuated by people who don’t know better.
What most people believe is that they need to know how to copy-paste code from StackOverflow or recite facts from a textbook. After all, that’s what the average engineer and student does.
But the reality is that to be a top tier engineer worthy to be hired, you must be able to do so much more than that. You must be able to move beyond the common algorithms and knowledge and learn some non-standard yet important techniques. And this poses a problem because this non-standard area is quite varied.
Let’s take a very simple example: Maximum sum of subarray of size K. The naive solution is to iterate through every index starting from index to index + K and sum up everything. The optimal solution is to just use a sliding window of size K.
Wait, sliding window? HUH? What college textbook is going to talk about a sliding window algorithm?
Even if you didn’t know it, the insight you needed to derive it is to reuse a portion of the previous sum. How were you supposed to know to look there? How do you derive that on the spot?
Don’t get me wrong, sliding windows are the basis for rolling hashes and are incredibly useful. It’s how text matching is done in such a fast time when looking up strings in a book. My issue is that you would only know about these if you either read a lot of research papers or had access to them in the resources recommended by the recruiters (which they are not).
Fortunately, the industry has moved away from these obscure patterns and more towards more solid interview problems that are more flexible. For instance, this red tape blue tape problem has a pretty clear answer. But what people miss is that the approach itself must be clean, clear, and logical (ie. how many different ways can the tapes overlap and what will you do about them?). This consideration and approach is what a top tier engineer does on a regular basis.
That’s what people really judge when they’re looking for good engineers, whether they know it or not. Consistent, clean, and logical approaches. Yet, this cannot be properly pointed out because seeing someone who can do this is more art than science. After all, how “logical” is logical? Does the person make jumps in logic? Does “clean” mean that a non-technical person can follow along? Does consistent mean that this approach will work for any other system? There is too much ambiguity.
This brings us to the second disconnect.
Companies have a major disconnect between what they say they want, what they mean, and what types of engineers they really need.
“Consistent, clean, and logical” is way too subjective a standard for companies to use as their official standard. After all, what is logical and clean to one person may not be to another. Esoteric language and fast-paced discussions can make a skilled engineer appear weaker than they really are if the examiner does not have the proper vocabulary or considerations to begin with. If a compiler engineer talks about desugaring and dexing to a product engineer, they are effectively speaking two completely different languages. While this exact scenario would never actually happen in a real interview, you can begin to see how and why small and different perspectives change drastically based on a person’s background.
Take this quote from Sheryl Sanberg, COO of Facebook:
“There is no truth. There is my truth. There is your truth. But the truth is subjective.”
If that is the official policy and stance of a Facebook executive, how can you develop a company-wide objective evaluation around a person’s skill, especially when the person’s skill is subjective? On one hand, you don’t want to get sued for discrimination so you standardize everything. On the other hand, proper evaluation of another person’s skill goes beyond a simple checklist.
This is where corporate standards become somewhat muddled but are not entirely incorrect. Companies will say that a person must be able to make tradeoffs, that a person should be able to think through the problem, that the person should be expressive and have good communication, etc.
But these are all approximations of what they really want. At the core, they are all symptoms of having a clean, clear, and logical thought process. But the company interviewer will most likely just be checking off a company-approved checklist and keeping his own subjective evaluation to a minimum. Companies want to hire the best, but cannot describe it.
The irony? Most companies don’t actually need to hire the best!
Because, funnily enough, most people are right! Most companies just need code monkeys to copy-paste code! The architecture at top tier tech companies is well thought out and created. All they need are people to write terrible, disposable code and ship disposable products, yet they hire as if they’re looking for the next Einstein.
In other words, what a company says they want, what they think they want, and what they actually need are different from one another.
This means that the interview is more easily understood but the goalpost has been confused by the gatekeepers. In the past, simply answering the question right with a vague approach or something that sounds remotely correct would have gotten you a free pass. Now, this is much less the case.
Fortunately, this is where this guide comes in to show you how to ace the interview consistently. But let’s say you don’t actually believe this point. Let’s say that the interview is purely random.
You know what the good news is? The less you believe that the interview tests your abilities as a software engineer and the less it is tied to the experience you have, the easier it is to fake being qualified. If you’re going to bitch about how bad the interview is, you need to accept this fact.
Because even if you need to memorize a few useless algorithms, the interview fundamentally tests how you decide which one to apply and why. This is absolutely true and I will prove this later on. The whole purpose of the interview is to test how competent you are as an engineer.
Maybe I haven’t convinced you. Maybe you’re still gung-ho about “LeetCode interviews are broken and don’t test anything.” Ok, so then it should be easy to game. And if it is a lottery, you can literally get an offer by not studying! See how well that works!
The less the interview tests your actual skills as an engineer, the more it must be an examination of some other irrelevant skillset. Therefore, getting good at that irrelevant skillset will make the interview yield a false positive. So go study that, whatever you believe it might be. Then once you realize you’re just plain fucking wrong, come back to me.
In short:
“You’re such a crappy engineer, how did you get such a high offer from x?”
“I tested well.”
In my experience, it seems like this industry has been nothing but a complete sham. Most people believe that technical interview preparation is in the education industry. Wrong. They are in the information selling industry. And they’re just as confused as the companies administering the interviews themselves.
Because of the aforementioned issues with the interview, the current state of them, and the flooding of people into the “information selling” space, a majority of information is relatively useless. Or are at least 5-7 years behind the times.
For example, take a look at this post from an interview prep website.
Someone please tell these guys that Buzzfeed-style listicles died 5 years ago.
I don’t agree that the interview has changed so much that it warrants the existence of all this new preparation material. Not to mention, how can you have an industry with so much more information and so much more content, yet people are still failing at higher and higher rates? It's just a flood of bullshit information that seeks to confuse people, not to improve their ability to execute. Truly amazing. The software information selling industry, mostly ran by software engineers who are professionally trained to actively solve problems, has managed to create a problem where there is none.
This “problem” exists because a big majority of the coding interview prep books and material out there just sell recycled information, regardless of the quality. The founders and creators of these products themselves have no idea how to execute on this knowledge.
Most of these resources are created by sophomores who are thinking they’re boss lords (brownie points if you recognized that as a Pusha T line). Do a lookup of how many of these interview prep platforms and companies are founded by people with 2-3 years of experience in tech before starting a business.
So what kind of content do you think someone with two years of experience is going to put out there?
At best, they are problem sets designed for a novice who is getting their feet wet with interview practice. But they do not do a good job at teaching people how to master the coding interview. They just talk about basic CS topics, walk you through some problems, and let you run off on your own. They’re basically a glorified test bank and simplified college textbook.
Allow me to prove my point with Cracking the Coding Interview (CTCI). Because I think it’s fairly mediocre as a study resource and it's pretty representative of the low quality resources that are out there.
Cracking the Coding Interview was published in 2008. The author, Gayle McDowell, worked at Google from 2005-2008. Prior to that, she only had internship experience with return offers from Microsoft. This means you are listening to someone with just two years of coding experience tell you how to master the coding interview.
Holy fuck. Why the hell would you ever listen to someone who has not even three years of full-time coding experience on how to ace every coding interview from junior to senior-level and beyond? Sure, her track record has shown she can crack the interview at an intern level. But I don’t think that should qualify her to put out a book that claims to know how to ace the interview on an industry-wide level.
To me, the book is just a “first mover’s advantage” in the information selling space. Nothing more. It's just a watered down textbook and with the bar of entry so low for information selling, it's only power is its brand. Its actually fucking garbanzo beans when it comes to effectiveness on its own and this guide (the one you’re currently reading) is worth more than that fucking book. Heck, even fucking toilet paper is better than the book.
Just my opinion. Don’t sue me, bro.
Are you kidding me? The “industry standard” was written by a junior engineer with not even three years of professional experience?
In all fairness, I don’t believe Gayle is the worst in the industry. Actually, she is an excellent business woman for having achieved this and at least that aspect is to be admired.
But the worst are the “gurus” and “influencers” who get a single FAANG internship, then proceed to get nowhere in their careers. They don’t ever land another FAANG job out of college or have an extremely low success rate at interviews. They never get promoted or improve. They only know one way of getting into a job: sending out a lot of applications, getting rejected on multiple phone screens, failing the majority of their on-sites, and then getting only one job offer.
The point here is that what I find most disingenuous about this entire practice is that a lot of resources hand-wave how to actually be successful in the process. They never say how to study or how to practice for the coding interview. They just say to study topics, practice problems, watch the videos they give you, and that if it fails, it was just a bad day and you need to study more.
I’m of the opinion that anything that has value can produce consistent results. That’s what I’m here to do. I’m here to make sure you can consistently get offers. And I don't believe luck should EVER be a significant factor.
Sure you can say 100,000 people have bought your product and claim that 1,000 people have received offers. But come on, do you really think you’re doing the world a favor with that 1% success rate?
I’m not saying that every “guru” is a fraud. Nor am I saying that only senior engineers are capable of writing a guide on how to pass these interviews. After all, if they are getting a FAANG job, they’re doing something right. But I guarantee you a terrible guide from a competent individual is 100x better than a well-written guide from someone who only got one internship or one job offer. More often than not, chances are the senior engineer will write a better guide than someone who only passed a FAANG interview once.
Just make sure that the guy you are listening to can consistently land offers and doesn’t wave around their one-hit wonder of a success as a mark of someone who knows what they’re doing. I could go in-depth into my issues with everyone marketing themselves but I digress.
Because it's not about knowledge. It's what you do with the knowledge that matters.
With that, let’s talk about you. Yes, you. Because even though everything around you sucks, you ultimately have the most control over the results.
You can blame the nature of interviews. You can blame the interview prep material. But that doesn’t help. Even if the interview is a load of horseshit, everyone is playing the game and there are people out there who can consistently score well on it. This is the game you signed up for so you have to play it as well if you want that job. So you might as well just play it to the best of your ability. If a bunch of qualified people can get hired by a FAANG company, there must exist some set of characteristics that should allow you to pass as well.
Bitching and moaning will not help. It’s like blaming FAANG companies for being incompetent because they won’t hire you. The reality is that you’re not showing you’re competent enough for them to hire you.
And maybe the reason for that is maybe because you ARE incompetent at showing them and you don’t know you are incompetent. If you aren’t consistently landing job offers or doing well on the technical interviews, you suck. Plain and simple.
“The incompetent cannot know they are incompetent. The skills you need to produce a right answer are exactly the skills you need to recognize what a right answer is.” – David Dunning
This quote by the guy who crystalised the Dunning-Kruger effect explains why I think it's basically pointless to review your own efforts or have your friend of similar skill give you interview feedback. Neither you nor him have the insight or skill to actually self-criticize properly. It is also for that reason an incompetent individual who cannot consistently get results is not qualified to give tips or write a technical interview prep course; they do not know what actually works because they’ve only done it once.
At best, you’re just going to guess and test approaches until you find a hit. There are better ways of improving and it starts by learning from people (or resources) better than you. Which is why you’re reading this guide.
With that out of the way, here are the common mistakes I see with candidates when they are practicing. I guarantee you that if you are not getting offers consistently, you’re making at least 1 of the following mistakes. These mistakes can range from your practice habits to your approach to the actual problem.
Fortunately for you, here are the solutions to those mistakes.
If you read any section in this guide, it's this one. Read this section. And reread it. And again. And again. Every. Mistake.
Because the easiest and most simple way to win at anything is to not make any mistakes.
We’re literally talking about changing your life and making an extra six-figures a year. For the rest of your life. You should be absolutely willing to put in extra 20 hours a week at minimum for 3 months to do so.
“I don’t have time” is a very weak excuse. I’m not asking you to dedicate 10 hours a day like entrepreneurs or professional athletes. They have to do that on top of playing their actual professional lives. I’m simply asking on average for 2-3 additional hours a day.
I don’t care who you are. Kid at home. Significant other. Whatever. You can find a spare 20 hours a week for three months if you want it badly enough. Literally it's the same amount of time it takes for you to go to the gym, work out, and come back.
If your doctor told you that you needed daily exercise or else you were going to die, you’d find the time. I would argue making an extra 100k every year (before compounding, inflation, etc.) for the rest of your life is a pretty close second. And I'm asking for almost the same amount of time.
And don’t half ass this. You need to put in your full concentration during those hours. Focusing on how to improve your problem solving and approach. Buying a course and doing nothing to apply it is an excellent way to waste your money.
Humans are creatures of habit. What you do in one interview, you will do in another interview. If you are approaching the interview properly and consistently, you should consistently receive offers.
However, if you are spotty and all over the place, that changes. If you breeze through the on-site for one company and fail the phone screen for another, you’re doing something terribly wrong. The only way to overcome this is with habitual practice such that your approach to one interview will work for all interviews. If you want consistent results, you need to have a consistent approach.
To do this, you do not have to reinvent strategies that have already been invented or grow it from the ground up.
Instead, you should use the consistent approaches, practices, and habits that lead to success. If you want to always get the right answer, you should copy a systematic approach that gets the right answer. That way, you can replicate this approach no matter what the problem is.
In fact, later on in the guide, I’m literally going to give the step-by-step to you.
Let me be clear: memorization of answers is actually less effective than ever before.
This is the case because of the dynamism of the interviewers and the trend towards more abstract questions with less clear answers or a less clear path.
So anyone who thinks that it's all about memorizing LeetCode questions is completely wrong or outdated by about 4-6 years. Feel free to fight me on this by memorizing the 400+ questions on LeetCode and see how many offers you get.
If it was that easy to simply memorize answers, not only would you be scoring offers left and right, you could theoretically score an offer far beyond your level simply by studying the right material.
Let me know when a junior engineer gets a staff level job simply by memorizing questions and answers. I’d love to watch the codebase meltdown because his “memorized” facts fail in practice.
“In theory, theory and practice are the same. In practice, they are not.” -Yogi Berra
Deliberate and consistent practice is how people become good at something. You don’t luck into being good. Without focusing on why you did not solve the problem and without an external influence that is more competent than you guiding you (whether that is the answer key or someone else), you’re just going to end up memorizing problems and answers with no ability to actually evaluate problems.
Furthermore, if you just keep grinding problems randomly and aimlessly, you will lack depth and understanding. Shifting your focus from learning from trees to graphs to searches back to trees is a really good way to get confused and to miss out on the nuances of a particular data structure or algorithm. The reason is that you’ll spend more time trying to remember these topics instead of diving into the topics and figuring out multiple ways of parsing it. Focusing only on a single topic for a good amount of time will make you an expert in a much shorter amount of time than spreading your focus and jumping from discipline to discipline.
It is also for this reason that I honestly think the whole “I got my FAANG internship after memorizing 100 questions of LeetCode” is bullshit. Put that guy through another set of interviews from the same company and he’d fail. The same guy advertising this is that same guy who gets rejected by Facebook but accepted by Google. That makes no sense: if you’re good enough for 1 company but not good enough for another of equal or similar status, then something is wrong.
There is an OCEAN of difference between doing 20 interviews and getting an offer on only one versus doing 20 interviews and getting 20 offers. It's like some guy who can hit a bullseye 20 times in a row versus some guy who can hit a bullseye 1 out of 20 times: one guy is going to the Olympics. The other guy is just going to be hanging out with his buddies with a beer at the archery range. Who do you trust to teach you to get better?
You want to be the guy who is acing every interview. That way you aren’t ramming your head against the wall wondering why you didn’t get the offer.
The one question I always ask any prospective client is “what steps have you taken to make sure you never make the same mistake again?” They often look at me as if I was speaking Hungarian and don’t know how to respond.
How can you possibly expect to improve if you never look at what you did right and wrong? Absolutely mind boggling. You’re just going to repeat the same mistakes over and over again if you don’t understand how and why those mistakes occurred to begin with.
Because, again, it's not about memorization. It's about application of knowledge, approach, and execution. And most importantly, not being stupid about the mistakes.
This is why you MUST keep a practice diary. Here is a sample that I make every single one of my clients use and it makes a tremendous difference. Note how meticulous and self-critical the notes are. We will discuss later on how to actually review your own mistakes properly.
But the bottom line is that you must record your mistakes and understand what decisions you made and why you made them. Understand if those decisions were right or wrong. And be absolutely ruthless and relentless in making sure you do not make those mistakes again.
If one decision caused you to not solve the problem, then you better make sure you never make that bad decision ever again!
I’ve actually found this to be the #1 reason why I drop clients: they simply refuse to do a self-critical evaluation. They just understand what they did wrong and then think they will fix it the next time it comes around. They are simply lackadaisical and don’t have an understanding of what it means to be absolutely relentless in fixing mistakes.
Seriously, if you’re not kicking yourself over making the same mistake over and over again, how can you possibly hope to improve? How does any basketball player hope to win games if he keeps missing free throws and never corrects this? Really examine every thought and step you make with a fine toothed comb. Everyone makes mistakes, even the best. If you find you’re not making mistakes at all, go get that Google offer and throw away this guide.
I can tell you what to do. I can point out your mistakes. I can boil it down to a recipe. But I can’t make you disciplined enough to fix your mistakes and bad habits. That’s on you.
If you’re repeating your mistakes, you aren’t fixing your mistakes. Plain and simple.
If you don’t care about your mistakes and say you’re going to do better next time, you don’t deserve to succeed at all. Because your attitude sucks.
This one is not so obvious. Most of the time, the seeds of failure are sown long before you actually fail. This occurs because usually you’ll pick a bad design or overlook some details in planning. Because your code was written with a bad premise and misunderstanding, it will fail test cases and therefore cause you to fail the interview. And you’re going to spend the entire interview just debugging your code when you really should have been looking back at your initial plan and proposal to begin with.
Bad planning (or not planning at all) is just one way you can get destroyed in coding interviews. There are thousands of ways to trip yourself up. So why not plan out how to get around them ahead of time?
The way to get around this is to really understand why you made every single decision you did. Why did you pick a data structure or algorithm? What characteristics of the problem lent itself to that. And so on. Every decision you make must be deliberate and intentional and only then can you begin to understand how the chain of decision making created the mistake.
And usually it just boils down to one tiny bad decision or one missed step that starts a chain reaction that causes failure.
What I see is most people trying to do everything at once and not fully understanding their own solution before writing code. This never made sense to me. How can you implement an idea you don’t even understand? How is your code supposed to work if you don’t even know how the algorithm works to begin with?
The most common form of this I hear is when someone complains that they have “too many ideas in their head.” Most people believe that great and fantastic ideas come from some crazy and weird convoluted ball of reasoning that nobody in the world can figure out.
Bullshit. It means you don’t know what you are thinking. Which means you have no idea what you’re doing. In reality, most clever solutions are actually born from a very few simple but somewhat obscure observations and facts.
The highest form of an answer and understanding is one that is easily understood and simple because its so difficult to misunderstand or screw up. If a child with no computer science education can understand your solution, you are on the right track.
Someone might probably screw up making a cake. But nobody is going to screw up instant ramen. God help you if you do.
Therefore, a great way to prevent coding before planning is to explain the solution as if you were talking to a five-year-old child. If a child can understand it, anyone can. And if anyone can understand the solution, it's much harder to mess up the execution. Simplicity beats complexity every time and it creates a structure that allows you to modify the details as need be.
The second tactic is to draw and test every idea out on pencil and paper. Humans are terrible at doing mental tasks precisely but are great at mental approximations. This is why algorithms that are created in a person’s mind fails so often: their algorithm doesn’t have an objective nor is a firm step-by-step solution. Or, they only work on approximate test cases and examples and fail on others.
Drawing it out with shapes and figures and testing the idea forces you to not only demonstrate the algorithm to the interviewer but also makes sure you understand what you’re doing. Not only that, testing will go a long way in proving its correctness.
You will frequently hear me on streams asking people to do both these. And these are absolutely a great mental tool to have in your toolkit, even outside of the interview.
A lot of people try to solve the problem in one go. They try to jump immediately to the optimal solution from the starting premise. Then wonder why their answer fails a bunch of test cases.
Jumping to the optimal solution immediately is very rarely the right answer or the right way to do things.
Humans are not that smart. We are only able to think about one idea at a time efficiently and we need logical, bulletproof steps to guide us from the simple ideas to the more complex ones. Societal progress is very slow for this reason.
The reason why is because there are details that are usually overlooked. Even one or two small details can blow up your entire plan. I’m sure we’ve all been there when writing code. Simply missing a few test cases can completely derail your code.
Let’s say we make jumps in logic and skip steps on a problem and jump straight to the solution. Then we can save ourselves maybe 5-10 minutes of planning and testing if we get the answer right. But if we get the answer wrong (which is more likely), then we have to start over anyway or waste time debugging and fixing things, often on the order of 20+ minutes. The risk reward ratio isn’t there: why would I risk 20+ minutes just to save myself 5-10 minutes?
This is why, in the real world, we would never build a product to handle every use case on the first iteration. No, we write a minimum viable product to handle the happy cases and then grow the system to handle the weirder and less common cases. The time penalty for getting it wrong is too damn high.
So why wouldn’t you do that in an actual coding interview?
Just start with the simple naive solution that you know works, eliminate the inefficiencies, and grow the problem to handle the next case while still being completely bulletproof for the previous case. That way, you have a step you can fallback to in case your next step is wrong.
Maybe you do understand everything. Maybe you do understand how hash maps works. Maybe you can recite time-space complexity off the top of your head. Maybe you actually know how to apply it in a meaningful way. Maybe you solved your coding phone screens but then you are wondering why they don’t invite you to the final round.
Good. Do it faster.
Where people make this mistake is that they assume that the coding interview is just like their college exams: just memorize facts, recite facts, and then pass the exam. Except this isn’t college anymore. It’s the real world and knowledge alone means nothing. You only get points for execution on that knowledge. And this is where your approach and ability to parse the problem comes into play.
When it comes to an actual problem, if you’re constantly spitballing random things to try to get a solution, you’re probably in the large majority of interviewees who don’t know what they’re doing and are trying random stuff. They just plain suck because they have no idea how to apply that jumble of knowledge to anything.
The application of data structures and algorithms should be autonomous and second nature. Similar to how you can talk without thinking about the words coming out of your mouth, you should be able to apply your knowledge in the same way. Even when you talk about your past projects.
If you have done your practice correctly, you should be able to run through all your list of questions and observations to evaluate the problem very quickly and efficiently. There should be no wasted effort, no pointless questions being asked, and every step you take should be efficient and get you one step closer to the answer. Anything else beyond that is inefficient and you can improve that.
This post on LeetCode does a pretty good job at explaining this concept of levels of proficiency and is also a very good writeup of a candidate’s path to an L5 offer at Google. To quote the post, the levels of proficiency are:
You want to be at the autonomous stage of proficiency when it comes to the whiteboard. But chances are you are currently somewhere between cognitive and associative.
The goal is to get to a point where you can look at a problem and almost immediately have 2-3 different ways of doing it and where one of the ways is the optimal solution. Or you at least have an idea of how to get to the optimal solution. The actions and evaluations you take should be autonomous and does not require a significant amount of thinking.
Here’s a great way to test if someone is full of shit: ignore them for 10 minutes. If you come back and haven’t lost anything of value, that person is full of shit. Great test to keep in real life by the way.
I love this one because everyone thinks the key to everything is “communication.” No, that’s wrong. Like anything in life, you need to communicate something of substance. Otherwise, its not worth listening to.
I guarantee you that if you are even a few years out of school, this applies to you.
Let me give you an example: take a video of your favorite influencer and every 10 seconds you watch, skip ahead 10 seconds of the video. I’m not going to link any video in particular to avoid starting drama but I think we all know some influencer who fluffs a 1 minute video with 10 minutes of mindless commentary, especially those who are in the finance space.
Now compare that to someone like Charlie Munger in this one: https://www.youtube.com/watch?v=J6sEcFYQh_o. The man speaks slowly but every word he speaks is important.
Who would you rather listen to? I mean, people who are not full of shit tend to do well in the long run and on average. The reverence for Charlie as both a brilliant investor, lawyer, and all-around modern deity of wisdom for decades should tell you something.
My point here is that if you tend to ramble on and try to “communicate” your thoughts and you do not advance the conversation, then your thoughts are probably shit. And since most people are full of thoughts, ergo, you are full of shit. If anything, the fact that you are full of shit is a symptom of the other mistakes and problems on this list. But ultimately, if you have a strong logical framework for approaching problems and you learn from your mistakes, you should be able to advance the conversation meaningfully and avoid this.
Despite its overlap, I just thought I’d add this one not just to poke fun at everyone. But also to put a god damn mirror in front of y’all. Because some people need it.
Why the hell should I listen to someone who I can just ignore for 20 minutes and get the same amount of information from?
Ok that’s enough of me insulting you. Let’s actually learn how to git gud.
Let’s talk a bit about some common principles that will apply, whether you are studying data structures and algorithms or system design. This section will explain my justification for content in general and the mentality you must employ when studying.
A good source of knowledge means a book or a problem set that trains the same algorithm, data structure, or idea over and over again. You will basically repeat the exact same skeleton of code time and time again until you puke.
For instance, there will be A LOT of problems in a row that practice your ability to iterate over two sorted arrays. There should be A LOT of problems in a row dedicated to binary search. There should be A LOT of problems in a row dedicated to having leading/trailing Linked List pointers. And so on.
Having all these in a pre-curated centralized location will save you lots of time because you won’t be out hunting the internet wondering what you should be focusing on. This is why I don’t recommend LeetCode until you’ve finished this guide! LeetCode is just too inconsistent in their solutions and very lackluster in their explanations.
Don’t worry, you will make back that money in a single hour once you land your dream job. Just make sure it’s the right one. And there are plenty of cheap quality courses taught by accredited professors instead of your favorite tech influencer.
This will apply for both the Data Structures and Algorithms portion and the System Design.
The way anyone improves at anything is by focusing on one topic over and over and over again until they cannot make any mistakes with that topic. That’s what you need to do.
Let me repeat that. Focus on one topic at a time. Pick a topic/section and work on it until you can solve every problem flawlessly!
That can be trees, hash maps, graphs, whatever. For me, my biggest problem was arrays. There are so many ways to manipulate an array and I always felt that choosing one of these manipulations was what was tripping me. Trees and graphs operate in pretty much a similar fashion so I naturally felt ok at both of them.
Wait, arrays? They’re just a block of data. How can that be complicated?
What you fail to realize is that while you can know WHAT a data structure is, being able to handle ANY and ALL manipulations of it is what you will be doing in the interview itself. And there are so many ways to manipulate arrays. That’s why “studying” doesn’t really work in the typical sense; you’re going to be asked to perform actions and executions over that data structure. THAT is the test, not whether or not you know what the data structure is.
Philosophy of Studying Mistakes/Improvement
This sounds boring but believe me, this is really important because it will help you focus on the right things when evaluating your own mistakes.
Let me first convince you of a simple fact. That it is simply better to avoid mistakes rather than seek excellence. This isn’t just me saying this. It's also billionaire Charlie Munger, Vice President of Berkshire Hathaway:
“It's remarkable how much long-term advantage people like us have gotten by trying to be consistently not stupid, instead of trying to be very intelligent.”
If he can become a billionaire by simply avoiding stupid mistakes, surely you can get a job offer by avoiding them too.
In my previous section, I have listed out the top mistakes I see. And there are many others out there that people make that aren’t listed. Note that the penalty for committing those mistakes, whether that is by studying or in executing on a problem, is extremely high and the reward for taking these shortcuts is extremely low. Usually, the penalty from committing these mistakes is failing to solve the problem and thus failing the interview.
Thus, we simply want to choose to study and approach problems in the most stable fashion. We work off of principles that are proven to work and focus on minimizing errors rather than choosing to express brilliance or flex genius level intellect to impress the interviewer. Because believe me, most of you are not that smart.
When you have rules, you create discipline. When you have discipline, you get consistency. When you get consistency, you can create and execute winning approaches. We want to create a framework for consistently and logically solving any problem given to us using a set of questions we ask ourselves and rules we follow.
Every time you study and review your mistakes, you will always be asking yourself how to incorporate that into your approach.
In short, our mantra is:
Next, let’s address the big elephant in the room. The Dunning Kruger effect.
How can we improve ourselves, on our own, if the Dunning Kruger effect exists? That is, how can we improve on our own results if we are not competent at seeing or identifying what we did wrong. If we are so fucking bad that we cannot identify our own mistakes, whether that is writing bad code or bad planning, how can we improve by looking at our own stuff?
This is where a good source of knowledge that can adequately walk through the solution comes in. We assume that the source of knowledge is absolutely correct and our goal is to see the results and get as close to it as possible. And it's even better if the source of knowledge walks you through the logic so that you can copy it.
What we will try to do is reverse engineer the thinking behind the solution. So it's vitally important that it's consistent at getting the optimal solution logically, consistently, and in a well explained and understood manner.
And by copying its approach, we clone ourselves to become as close to optimal as possible. We adopt different ideas, strategies, and questions into our thinking in order to better improve our approach.
This also means we don’t blame execution or the final step. It's absolutely meaningless to say “I should have been more careful.”
Why? Because it's vague and doesn’t really target a specific area for action.
My belief is that mistakes come from being put into the wrong situation or making a lot of bad choices prior to the mistake. If you say something like “I should be more careful when writing” or say “I could have executed better”, that’s not helpful.
How careful is careful? Should I retest everything over and over and over again? Why would I do the exact same thing over and over again?
And how could you have done it better?
Again, there are many many factors that lead up to a mistake. And we want to eliminate those factors with good habits that apply across the board.
Randomly doing things just to avoid one mistake or corner situation can lead to a lot of issues.
Good preparation makes avoiding mistakes easier and so we want to optimize the preparation. The planning of the code, the design, the understanding, the testing, the code coverage. All the areas should be scrutinized and thought about, whether that is being too slow or missing out on something.
In the coding interview, you will have a data structures and algorithm portion. If you are testing at a higher level, you will get a system design portion as well. Let’s focus on the former.
For this section, I will mandate that you pick up the following two materials: Grokking the Coding Interview (Grokking) and Elements of Programming Interview (EPI). These two are the best interview prep material I’ve seen so far and are the basis for this guide. And no, this guide is not sponsored by any one of these books and courses.
You must complete at least 60% of Grokking before you go to EPI. DO NOT STOP AT JUST GROKKING. GROKKING IS JUST STEP 1 OF THE PROCESS.
We will cover how to actually use the Grokking resource. Because it's more than simply just doing problems. Note that you must constantly be evaluating your mistakes as you do this.
If you want to see me do an example of the below steps, please watch here. You will repeat these steps for every topic you study. Fortunately, Grokking divides the sections up into their own topic. And remember to stay with a single topic until you master it completely!
There are many algorithms out there that you won’t see in your textbook and are really only useful for your interview (sliding window, 2 pointers, etc.) because they are not academically interesting or worth researching. But your job is to master them anyway, such that it is second nature. Unless your interview is scheduled for 2+ months in advance, don’t worry about retaining it though. Once you’ve mastered it, your subconscious will retain it long enough for you to actually do well on the interview.
The implication is that the algorithms themselves should become second nature. Writing a binary search or DFS should be a breeze and you should be able to do it on command without thinking.
For me, this means that I will rewrite the bare bones of the pattern over and over and over again until I can do it automatically without thinking. And maybe a few more times after that.
You probably think of algorithms as a way of performing an action. They are just fancy steps you follow, like a cooking recipe, to get an answer. You are probably memorizing an algorithm, how to implement it, and then its runtime and space complexities.
No, that’s pretty much backwards. Think of an algorithm as a way of getting a desired outcome based on a series of preconditions. Once you meet these preconditions, you can apply the algorithm to get the desired outcome.
Djikstra’s algorithm is a way of finding the shortest path between two nodes of a graph. This means that Djikstra’s algorithm can be organized as follows:
Preconditions: An undirected graph, positive weighted edges
Desired Outcome: Shortest path
This means that if you see an undirected graph and that graph has positive weighted edges and you want a shortest path, then you can apply Dijkstra’s algorithm. However, you must ensure that all these conditions are met first.
Another example, mergesort.
Preconditions: Able to use lots of memory, cannot tolerate a O(n2) worst case scenario
Desired Outcome: Sorted list
Why do we think of things this way? Because it allows us to assemble solutions from the ground up using very simple properties. That way, if someone asks you to justify your decision (ie. why merge sort over quicksort?) you will be able to understand and explain why.
Ok seems simple enough. Now let’s try something that’s a little less textbook-y and more interview-like. Consider cycle sort. It is an unstable in-place sorting algorithm that runs at O(n). However, there are certain conditions that must be met before using this.
Preconditions: An array, can translate values into positional values, and values are sequential
Desired Outcome: Sorted array in O(n) time
In order to actually know what and why these are the preconditions, you need to actually understand what cycle sort is and you need to break it down and fully understand each step. Personally, when I studied this, I modeled this as a graph with connected nodes and positional values such that they became a cycle. You must also be fully aware that O(n) time is the next fastest time complexity after O(nlogn) which means you must know that the next slowest option is something sorting-related (since most of them are O(nlogn)).
As you practice each algorithm over and over again, you should actively understand the preconditions, how to write each algorithm, and the result you will get. The preconditions are the most important, NOT the algorithm itself. The algorithm is important and you should be able to do it enough times to write it off the top of your head. But it is so much easier to check these conditions than to throw a data structure or algorithm at it.
You can either check if a graph is positively weighted and if you need a shortest path by asking the interviewer or parsing down the problem which takes 15 seconds. Or you can try and randomly throw Djikstra’s algorithm at a graph and waste five minutes.
Furthermore, some of these rules can be optimizations on top of naive solutions. Consider dynamic programming and the Fibonacci sequence. The naive solution is to do the problem recursively. An optimization of it is to realize you can reuse certain values and you cache previous results into an external data structure. Finally, you optimize over that and realize you can use a dynamic programming array where the dimensions of the array are the values that change as you recurse into the function.
But it's about being able to see optimization on top of optimization that gets you there. Each one of these steps is a precondition to the next step.
Data Structures can be thought of the same way. They are ways of organizing information efficiently based on certain preconditions and very rarely should they ever be dedicated to a single data type. Let’s take a trie. The characteristic of a trie is that sequential subsections of each data overlaps.
Preconditions: Multiple values that share sequential subvalues, O(n) lookup time
Desired Outcome: Efficient storage of all values
If you apply this, you can do question 2 in this guide. Instead of thinking about exclusively thinking about a trie as a way to store common partition of words, you can think of them as a shared pathway.
That being said, this isn’t necessarily baked into the common definition of a trie but there is nothing that says each of the nodes in that tree can’t be numbers or other values. Strings are just one possible one, in which case you can think of a trie as sharing substrings.
Now, let’s tweak those conditions. Suppose that I tell you that I want a constant lookup time instead of O(n). This leads us to a hash set which is faster but we trade off memory space for it (consider why that is).
By doing things this way, we organically grow our solution from the smaller properties at hand. That’s all these interview questions are: a set of beginning properties that you have to assemble into a clean combination of data structures/algorithms.
As you master more and more techniques, you might end up using the wrong technique because some of the preconditions/rules clash with older techniques you learned.
Let’s actually qualify this phenomena in a 4 stages of competency chart. You will start off at 1, trying to figure out what the algorithm or data structure is about and why/how you should use it. You will come up with a few rules that will be your hints to decide when to use the algorithm or data structure. Then you try it out against your current problem, then the next problem and so on. This is going from 1 to 4.
But your biggest problem won’t going from 1 to 4. You’ll be doing that several times over with each new technique. The biggest problem is going from 4 to 1. As you learn the nuances and patterns for each technique, you might end up learning the “wrong” thing or learn an incomplete set of preconditions. While it might work for now, it may clash with other future preconditions. If you rely on the wrong insight, that will make you apply patterns in the wrong way or at the wrong time.
In short, the conditions you pick for each data structure/algorithm can overlap with others.
Going back to the cycle sort, you might incorrectly use it when it is better to use a heap if you're looking at a sorting problem. You might forget that you need to have the elements be translatable to positional values. Or maybe you didn’t take that into account.
For a trie, you might just misclassify it as a tree that represents strings and only strings since that is the textbook way of teaching.
That’s fine. What is more important is that you can course correct quickly, especially during the actual interview. The only way you actually course correct quickly is by encountering this confusion in practice.
To mitigate and fix these, if you think you’ve come up with a new precondition(s), backtest it against problems from the last five or so patterns you have done. Usually the problems are grouped by the data structure they are applied to so it exposes you to more problems where you might actually make that mistake.
Make sure it doesn’t confuse you or make you mistake the usage of one algorithm for another. Most importantly, make sure that preconditions you use for the algorithm can consistently lead you to the right answer.
As you backtest, make sure you actually understand what is happening and WHY. Understand what the implications are for misusing the algorithm because sometimes that is what you want.
As an example, consider a problem like, “Find the missing number in the following scrambled array: [0,5,4,1,2]”. While we can sort the array and iterate over it in O(nlogn + n), let’s try cycle sort and get O(n + n) time. Since the preconditions were that the numbers were sequential and directly translatable to positional values, it makes sense that cycle sort could be used. It might not work but let’s give it a shot.
What you will find is that if you apply cycle sort to this algorithm, your array will look like: [0,1,2,5,4] where 5 occupies the space of the missing position.
The point I am trying to make here is that you need to and should make mistakes in misusing the techniques you learn. You need to try them against different problems because those mistakes are part of the process as well. So embrace it.
You only arrive at this step once you can do over 80% of the problems of a single technique without any help. You know what nuggets of information to look for or how to strip the problem down into what you need. You can “sense” the heuristics needed for the problem. You know how to write the skeleton of the code off the top of your head without thinking (level 4 mastery) in the most efficient way possible.
For you Java programmers, that means using Java 8 lambda expressions for comparators in the constructors and abusing getOrDefault() and putIfAbsent() (consider why in terms of code efficiency).
If you have done all the problems in the problem set and you still can’t consistently solve them with the handset of rules you have, go back and do it again. And again. If you can’t derive a rule for the technique, then at least memorize it and maybe a few of the problems AS AN ABSOLUTE LAST RESORT (time constraint).
Are some techniques absolutely useless? Yes. Cycle sort is one of them. I would say you almost never run into a real world situation or even a whiteboarding interview where cycle sort is the expected solution because of how nuanced and weird it is. However, you should be aware it exists at the very least and give it a try.
Why? What you will find is that because you are constantly evaluating problems to find these preconditions and rules for the algorithm, you also practice your ability to break down a problem. The more you practice breaking down the problem in the exact same way, the better you will be at it.
This is what will save your interview 9/10 when the patterns you have committed to memory don’t work. Not only do you have the patterns and templates in your mind that you can use and solve problems very quickly, you also have your raw skill.
And all you had to do was do the same thing over and over again.
If you have been following what I’ve written faithfully and completed Grokking, then you should be at a point where if I gave you an infinite amount of time, you should be able to solve any problem.
But now, what we want to do is improve our efficiency. Improve and avoid any mistakes we might have made. Any incorrect thoughts, etc. How can we properly learn from our mistakes?
I’m going to show you how to optimize your problem solving structure. While the above basic way of problem solving is still the basis, what we’re going to do today is to learn how to improve upon it ourselves in a way that makes sense to us.
Note that this section doesn’t exclusively apply to EPI. If you find yourself struggling with a particular topic, you can apply these steps to Grokking as well.
For the lazy, are some practice diary from past successful clients
I have a playlist of me solving a few problems and performing this exercise of evaluating my own mistakes here as an example.
Here is a checklist for your practice diary to make sure you are recording your mistakes and entries properly.
Note: If you are hoping to become a client, I WILL NOT accept you until you show me a practice diary that you have done on your own.
Get a timer and an audio recorder because you are going to need to time and record your problem solving. You don't necessarily have to be on a google doc. Pencil and paper do just fine as long as you know at what time stamp you wrote what idea.
For me, I usually have OBS running recording my ipad/Google doc and my microphone set up. I will have the timer shown similar to what I do for streaming but you can use your computer’s timer or clock.
I also encourage you to speak your thoughts similar to how you might conduct an actual interview. But most of your actions and thoughts should be on the paper. If you didn’t write it, it doesn’t exist.
Plus, speaking your thoughts aloud is very helpful. If you watch my coaching videos, you’ll notice I preach talking through the problem and running through examples verbally. This helps the interviewer understand what you’re thinking and it helps you walk through your own thinking instead of doing things in your head.
Third, and this is really important, you need to actually try to solve the fucking problem. Seriously, a lot of people just try to look at the solution only. Don’t do this. Actually take an earnest attempt at trying to solve the problem with your systematic approach. Don’t be fucking lazy.
Finally, once you have had a good run at the problem, then we can begin the actual evaluation. You have your attempt at a solution, your record of how much time you took, and then the sound.
With that, we’re ready to begin. You have your attempt at a solution and the recording of yourself.
What you need to do now is really dissect every decision, every thought you had when solving the solution. And this comes in the form of a bunch of questions you need to ask yourself. That’s really all this process is. Watching your own replay and obsessing over efficiency.
This is a list of questions you should ask yourself at minimum.
You would be so surprised as to the number of times I’ve heard people tell me that they don’t even know what they were thinking. If that happens, then chances are you had no direction when solving the problem. If that was an actual interview, it would be an instant fail.
At no point during the interview are you allowed to be in a position where you cannot explain your ideas, solutions, or direction to a five-year-old child. Simply being able to do this is a great way to focus in on what you need to do and help the interviewer follow your thinking.
Can a five-year-old child understand what I am doing at any moment in time? And can you convince a five-year-old child that the idea works?
This has a lot of implications. First, you should make sure that the most important ideas that you think about, test, and say should be written down. Nothing more, nothing less. If it didn’t make it to the paper, it didn’t exist. This heuristic will help you from rambling aimlessly as well.
Second, make sure that your tests are explicitly tested on the paper. No idea should ever be committed to without a proper runthrough and testing. If there is no test plan or testing that was written down, it does NOT EXIST and you must correct this.
Third, you must be able to draw your solution out step-by-step using a stack trace. You can find an example of me doing this here. But you must be able to walk through the idea, say explicitly what the value of the variables are at each step, and draw out at what step in the function you are in.
“It's very simple.” “Then explain it.” “Now that's impossible! It came to me in a dream and I forgot it in another dream.”
Firstly, compare the difference between the optimal solution and your solution. Walk through how you got to your solution (or your attempt at one) and compare it to the steps the optimal solution takes.
Because we emphasize a good source of information, we assume that the optimal solution in the book is the best one in the universe. And we want to try to get as close to the optimal solution and execution as possible.
Every little decision that you made vs what they made should be compared. Its all about emulating the steps and trying to figure out why they took the steps they did and how you could have avoided some of the mistakes you made.
What were the steps that the optimal solution took? Did I try to explore those? If I missed an idea that the optimal solution had or a step, what would it take for me to recognize that is the next step?
On top of that, when looking at your own solution, you should be asking yourself:
Were there thoughts and paths you took that didn’t lead to anything? How can you avoid these? What did you need to see first and how can you have a better intuition to see that insight?
Or if you didn’t get the solution at all, what were the insights that you needed to even get started? Is it logical and simple that anyone can figure out the step that you got stuck on?
And, as a big point here, very rarely is an optimal solution born out of thin air. Usually there is a logical series of thoughts and steps that can be built on top of each other to get to the optimal solution.
So you should try and look for places where you tried to do some weird hail marys, where you tried to throw random stuff at the problem? Did I try to yolo the code? Did I try to do a magic bullet? Could I avoid this? Is there a logical train of thought that can prevent me from doing so?
Once we have that down, the next thing is to look at your own replay. I recommend watching this at 1.5x speed and record the timestamp at which you express certain thoughts.
Now look at your time stamps. Look at where you spend a lot of time. What thoughts stalled you.
What did I spend too long on? How can I avoid wasting this extra effort and time? Was there extra thoughts that did nothing? Did I overemphasize this set of thoughts? What did I not spend enough time on? Could adding more time here help prevent mistakes that I made later? Should I invest more time into this area to ensure I am at a lower risk of mistakes later on in this step?
This part is tricky because too many times, people have a habit of cutting corners. So for this part, you should be asking yourself how to improve your time without sacrificing the quality of your design or planning. You want to eliminate inefficiencies without increasing your risk factor.
It's about seeing how much time you can reallocate from one area to another to ensure that there is less risk, less chance of mistakes, in other areas. If you invest more time into design, you will have less risk of writing buggy code. But there is a point of diminishing return as well.
If you cut too much time in design though, it can come at the expense of your code becoming sloppy and buggy. So this part does require a bit more finesse and practice.
Finally, after all that, we examine the code itself. Because we follow the philosophy that good design leads to good code, we always critique the steps leading up to the code itself. Once that is clear, we can examine our implementation.
The questions we ask ourselves are:
Why did I write this bad line of code? Why was this comparison wrong? Could I have planned this better? What were the decisions that lead up to this bad code or bad line? Could I have made a better decision earlier up in the chain?
What edge cases do I need to look out for? Could I have tested for this? Was this obvious earlier on?
If there are too many edge cases, can I simplify my design and approach to be more systematic so I don’t have to worry about remembering so many edge cases?
You see me do this in my own video on how I study. I state how there were some execution mistakes in my design or that I wrote things too sloppily that confused me that lead to bad or mistaken code. And its because I ask myself why I made the mistakes I did. I conclude that simply organizing my own work and avoiding global variables would have helped to prevent me from making the silly mistakes that I did.
Notice across all the questions, I don’t ever blame the execution. I always blame the steps leading up to the execution. Like I said before, we focus on the planning and the setup.
So instead of saying vague things like “I should be more careful,” we should make these actionable during our planning. For instance, actionable and proper observations and improvements are:
“If I organize my space a little cleaner, I can avoid writing an incorrect comparison operator”
“If I do a test by cases, my conditional statements will be cleaner and easier to manage and independent”
“If I try to draw a logical line of thinking from start to solution, I don’t have to try magically yoloing my code”
“If I am doing iteration, I should be aware of boundary conditions”
Each of these thoughts are based on avoiding the mistake to begin with. And if you have enough factors in your favor, the execution and decision becomes so much easier. So do try to answer the questions I have listed in this manner.
Make every answer actionable and target a part of the planning. Ask why things are the way they are and what series of actions lead to the mistake and attack the easiest and most obvious action.
And most importantly, write all your fucking mistakes and lessons all down. Record your mistakes in a practice diary with the problem, the amount of time you spent on the problem, and the lessons you learn from them. This will help you to remember your past mistakes and lessons in the future. And be as detailed as possible!
That said, the final point I want to drive here is that this takes a lot of time. A lot. You can easily spend 1 hour on a problem with self evaluation, self critique, and solving. A far cry from the 20 minutes you might be used to spending on doing a LeetCode problem, giving up, and looking at the solution.
But I really encourage you to try and spend the time to do so. These lessons and feedback really add up. You might learn the wrong ideas or lessons and need to correct them. But eventually, you’ll find yourself executing almost flawlessly because you’ll have learned, iterated over the bad ideas, and kept improving the execution.
Of course, if you are still struggling to do this on your own, you can book me for coaching. The difference between you doing this and me is that I’m much faster at this and more accurate.
Not only because I know the answer, I am much better at identifying proper decision making weaknesses a lot quicker. I’ve been through this process not just with myself but also other people. And since the interview does adequately judge your decision making as an engineer, I am also very well equipped to know what a good engineer does and what a bad one does.
Now, if you need a solid example of all this in action, I’ll link these videos here. In these videos, I solve a problem and then I compare two solutions that my clients submitted. I ask myself these questions and ask at each step what could be done better as if I had submitted the solutions myself. I compare what the optimal solution was to what they are doing and judge them on their efficiency and what they wasted a lot of time on.
TL;DR: You suck so just try to make sure you suck less with each problem you do and don’t make the same mistake. After 100 problems, you’ll be pretty fucking decent.
So now you know a bunch of algorithms and data structures off the top of your head and can instantly pattern match the obvious and easy problems (ie. Combine 2 sorted arrays). And now you can review your mistakes and find out what you did wrong.
But now you’re wondering how to put it all together. After all, not every problem you solve will be solvable by a pattern. So what should your initial approach be to solving a problem?
This section will give you a basic blueprint to solving any problem. Note that you will still need to review and study the problems after using the study techniques described in the previous section. This is simply a starting point that can be optimized using your own heuristics and observations.
So here are the rules of operation. It's basically codifying all the vague tips and tricks people have been telling you to do but you never really understood when to use them.
There are four hardcore principles we must follow:
At any point if you violate these principles, note that you are making a mistake and you must figure out why you ended up in that situation when you go to review your answer.
Approach:
1. What is the simplest, easiest way I can solve the problem?
2. If I cannot figure it out, let me generate a few examples. What are the patterns generated from the examples? What will the algorithm look like? What is the simplest naive solution?
3. What are the short comings of this algorithm? Is it taking up too much time, space? Or is the resulting algorithm and code messy?
4. For every inefficiency, how can I fix it?
Let’s dive into each one of these steps and see what they entail.
This is the naive solution. Your “I know this will work but it's probably a hot garbage” solution. There is nothing wrong with starting off with a solution that you know works because that means that there is something in there that can help you solve the problem.
Where most people mess up is by skipping this step and trying to solve the problem in one go. That is extremely difficult because there are often many corner cases that need to be accounted for. Corner cases that can simply be avoided by handling the main case first in a simple manner and then just adding one or two modifications.
If the problem can be solved with a pattern, you can easily apply it here. However, keep in mind that if you can instantly solve it with the pattern, you must repeat the exercise with the naive solution. The pattern you learned arises because of characteristics that stand out in an inefficient solution. So you must be able to go back and recite the step-by-step of that as well.
If it's impossible to figure out right off the bat or if a pattern does not apply, you should then try to mechanically solve the problem. This means simply trying a few examples, looking for commonalities between the solutions, and then abstracting what works into a simple workable solution.
There are various techniques that I use and you will develop your own with time. In fact, in the previous version of my guide, I laid this out. I’ve omitted it here because the discussion is too esoteric and deviates too far from the objective of this section.
The easiest and most simplest way of doing this is just going through examples, drawing each of the examples out step by step, and looking for patterns.
Oh, and remember to explain what the solutions are and why they work as if you are talking to a five-year-old while you are doing this. It will be much easier to nail down what actually works and what is just your conjecture and imagination.
Usually, your first (or even second or third) attempt at a solution is going to consume more time and space than necessary. This makes it inefficient and you need to be able to correct these.
Consider solving a problem by iterating through a tree twice. Doesn’t that strike you as inefficient? Maybe there is a way to solve it by iterating through it once.
There are many dimensions to inefficiencies: time/space utilization, repeated steps, code cleanliness, etc.
For time/space complexity, here are the order of operations and their implications.
O(2N , 3N): Permutation
O(N2 , N3…): recursion/generate subsets, nested loops
O(N log N) : Sorting/heap
O(N) : Iteration, pointers/sliding window, common array operations
O(log N) : Binary Search/ Exponentially sized steps search
For instance, if you find your solution or part of your solution is N2, then you should try sorting, iterating, doing a binary search, or some combination thereof to improve your solution as those will be faster run times.
For repeated steps, simply ask yourself if you’re repeatedly iterating over the same data or using up more space than you really need to. Usually, you can combine steps together for a faster runtime.
For code cleanliness, this speaks for itself. It's the least important dimension but it's nonetheless something to look out for,
Where most people screw this up is by not connecting it to their naive solution. That is, they know they need to improve the space complexity but then don’t modify their original solution to do so. Going back to the tree example, if you know you can solve the problem with 1 traversal of the tree, maybe don’t quite abandon the traversal idea just yet.
Given a number n, find the smallest number that has the same set of digits as n and is greater than n.
Example: 1234 -> 1243
1. We will be moving digits around and it doesn’t really seem like our simple math operations will help. So we can think of these as an array of numbers and throw every data structure we can at it. Heaps are glorified sorts and sorting doesn't help. There is only one array so we can’t do a 2 pointer iteration through two sorted arrays. We could use a sliding window since there is one array but it is not immediately clear how it would be used, especially since it doesn’t appear we need a subarray. It’s not immediately obvious where trees or graphs play a role here.
Permutations seems to be a very naive way of solving it. That is, we generate every possible number, sort, find our original number, and then get the next largest. This forces us to use 2n space and n2 time. Both of these seem extremely inefficient and gross but it's a solution at least.
Optimizing on this isn’t very clear. How do we know what elements to cut out? Is there really unnecessary repeated work that we are doing? Alas, we arrive back at where we started and need to start from scratch.
2. Let’s generate some examples then
4123 -> 4132
4321 -> 4321
32876 ->36278
32841 ->34128
So we know that if the array is sorted in descending order, it is already in the maximum integer. (4321) Let’s see if we can follow that logic: what happens at the point where it begins to ascend again?
4123 ->4132
4321 -> 4321
32876 ->36278
32841 ->34128
Based on our examples, we see that the first digit that changes is the first element that is one whose neighbor is greater is the number you want to change. You’d substitute that number for the number on the right hand side that is the next highest. For instance, in example 4123, you’d see that 1 is smaller than 2 and mark that for change. Then take 123, replace 1 with 2, and then sort the rest of the numbers in ascending order.
4123 ->[41][32] (Corner case: looks like sorting 23 does nothing. Should replace with descending sort?)
51234 ->[512][43] (Built off the above case. Looks like an edge case where if sorting after the pivot does nothing, then just swap the last 2 digits)
4321 -> 4321 (No-Op)
32876 ->[36][278] (278 is sorted)
32841 ->[34][128] (128 is sorted)
That looks like the case and would work. Can we do better? Sorting requires O(n log n) so we must find something that requires scanning O(n) or binary search O(log n) time since those are the only times that can beat it.
Let’s go back to step 1. This time, this is a sub problem: how to sort a list faster than O(n log n)?
1. You should already know all your primary sorting algorithms cold and know that the average case never beats O(n log n). We can never sort by log n so therefore we must sort by O(n). Therefore, there must be a certain characteristic that allows us to be faster than O(n log n). Cycle sort is a O(n) sorting algorithm but we don’t see a guarantee between position and value. The immediate characteristic isn’t clear.
2. Let’s try pattern matching then. After applying our first step, we get something like
32876 ->36872 (swap 2 with 6) ->[36278 is desired]
32841 -> 34821 (swap 2 with 4)->[34128 is desired]
So our numbers of interest are 278 and we know that the numbers on the right hand side are sorted but in the opposite order.
Let’s then rephrase are problem: instead of sorting faster than O(n log n) how do we reverse a subarray?
1. If we reverse the subarray, the first element becomes the last and vice versa (first in, last out). We can solve this with a stack or 2 pointers that advance towards each other and swapping. Remember those preconditions? If you studied your stacks enough, you should know:
Precondition: First in, last out
Desired Outcome: Reverse list of elements
And voila, you just passed your interview. Just write the damn code now: first find the part where the number sequence starts ascending from left to right and swap with the next highest number on the right hand side. Then reverse the right hand side.
Before you continue, I will mandate that you pick up the following 2 materials: Grokking the System Design Interview (Grokking) and Designing Data Intensive Applications (DDIA). These two are the best interview prep material I’ve seen so far and are the basis for this guide. And no, this guide is not sponsored by any one of these books and courses.
Note that you will NOT be asked System Design questions if you are applying as a junior engineer.
The system design is a very difficult beast to understand, mostly because the average engineer approaches engineering in a very braindead way. He writes code for the sake of writing code. Nothing else. No problem to be solved. No objective. Nothing.
Sad really. But ironically, in my opinion, it's the majority of the “senior” engineers in the industry.
Consider the following:
When you’re asked to build a product, what would you do? 9/10 engineers would say they slap together code, glue APIs together, stack overflow everything, and copy a random tech stack they saw online and ship it. Maybe they’ll add a few things like load balancing and configure and nginx proxy because someone on Stackoverflow said to. All without truly understanding what happens underneath and call it a product.
What’s wrong with this approach? Well, who’s going to maintain that product when you have more users? Nobody wants to maintain dogshit code/architecture and certainly dogshit code/architecture does not scale.
Scaling dogshit involves producing more dogshit, both figuratively and literally. I pity the poor fucking dog who has to keep pooping that out though (again, use your imagination as to who the dog is in the figurative scenario).
But doesn’t this approach sound familiar? It's basically what a lot of failing candidates do for the algorithm portion of the coding interview: throw random patterns and data structures at a problem until it works. There is no objective thinking.
And it's no wonder that people fail the system design for the exact same reason. They don’t design a system: they create a product that “just works.”
So don’t sell me on that “system design is only about information and knowledge” bullshit. If it's anything like the coding interview itself, it's about the application of that knowledge in a meaningful way.
And when it comes to a nebulous and unknown system that has no real solution to them, this is doubly true.
More often than not, I see candidates fail both the system design and algorithm for the exact same reasons. If they only fail one or the other exclusively, it is almost always a lack of knowledge. Maybe it's because they have no experience with certain tech or they forgot what they studied in university long ago. That usually can be solved by simply reading more blogs, reading the actual core code, playing around with a tech stack, and deep diving.
Why might this be the case? Because both system design and algorithm interviews are the same at their core: you are trying to prove that your solution works and is optimal given the constraints of the problem. And you grow that solution from a naive one and then optimize and eliminate the inefficiencies. The only thing different is that you’re using a hashmap instead of a load balancer. You’re using 2 pointers instead of Elasticsearch.
What system design requires compared to algorithms is much an appreciation for nuance that is ultimately born from your decision making. There is no such thing as a free lunch and every component you pick and choose to use will have benefits and drawbacks. The whole interview is you wrestling with those decisions and trying to design something that can combat the downsides you’ve chosen to accept.
As an example, if you want to make an argument for MongoDB or Cassandra, you need to be able to appreciate the differences between the two when it comes to scalability, performance, and a multitude of other factors. You also should at least have experience implementing 1 or the other and have been bitten in the ass in some way since no decision or tech is ever perfect.
If you chose the wrong database, blew up your project because of it, you’re never going to forget it or the reasons for it. All that will show through.
If algorithms is chess with a finite number of deterministic choices and moves, the system design is Go where you negotiate territory and pieces between you and the opponent with moves and stones.
Which is an excellent metaphor for the system design: you are negotiating with the problem, accepting certain benefits in exchange for certain drawbacks so that you can best solve the problem at hand.
This brings us to how one should actually approach the System Design interview. For now, we will only focus on the classic System Design that consists of choosing components. But the fundamental ideas apply no matter what system design type you are doing (ie. if you were asked to design a system to deploy language translated strings to a mobile binary).
Let’s start with why we ask system design questions. Why would we ever ask a candidate to build a theoretical system that they’ve never built before? Certainly most engineers have never had to design a version of LinkedIn for 1M users.
However, most L5 engineers have seen the shortcomings of bad decisions when modifying systems and, in general, should know what to look out for.
And at FAANG specifically, this is essentially what we’re working with. An engineer needs to at least have a cursory understanding of what parts of the techstack can go right or wrong and why. If he cannot choose the right way, he can at least reason about the 1000 wrong ways because he’s seen them.
But you don’t have to be a devops or tech lead to have this knowledge.
As a product engineer, it most likely means navigating a code architecture and having a cursory understanding of how the layers interact with each other. Knowing that you might want to battle test a system by periodically injecting synthetic failing requests. Knowing how to handle sending data back upstream in a microservices architecture and choosing an appropriate TTL. Knowing how the data models interact.
While that all sounds super crazy and a lot, the fact of the matter is that it's relatively simple if you have any understanding of how data flows end to end. There are a myriad of system design interview solutions you can find on YouTube but I believe this is a fairly good example:
So the reality of the system design interview is that it's actually a behavioral interview: it's a behavioral interview disguised as a technical problem. The behavioral part is the questions and considerations you ask when trying to prove or build up your system.
How do you want to organize a particular subsystem? What are your options? Why did you pick the component or design that you did? What are you willing to give up? And how did you justify it?
The depth of your knowledge and the types of questions you consider will actually determine what level you are. More on that later.
Consider 2 candidates: Candidate A proposes a solution that uses the latest and hottest tech stack, stating the benefits of every component he is using. He argues that with such a robust tech stack, it will be able to scale well.
Candidate B creates a solution that, while not optimal, addresses the primary pain points of the problem and delivers the minimal function. But he doesn’t know what a NoSQL or MySQL database is.
Which candidate would be better?
Some might think it's the first. After all, he really knows what he's talking about with all these tech stacks. He has experience writing with these stacks. He knows what we need to write a product!
The problem is that the tech stack or design might not even fit a person’s needs.
For instance, if I asked someone to use MySQL vs NoSQL or MongoDB and Candidate A just said to use NoSQL because it scaled better, I would ask why. If you couldn’t answer that and just say “it just does”, then you fail to understand the idea that “there is no such thing as a free lunch.”
Because there is no magic wand solution. There very rarely is without a significant tradeoff.
What if your use case primarily needs to do a lot of relational joins and pull data from multiple tables? The performance from MySQL would blow MongoDB out of the water. Doesn’t matter if it “scales” if you can’t tell me how or why it scales. Or even if scaling along that performance axis matters.
Yes, there do exist NoSQL databases that can effectively act like a MySQL. But those have a subpar performance. It's akin to driving the Indy 500 with a Prius engine. Will it get you around the track? Yes. But it's not going to do it well at all and once you hit enough traffic on your system, you will start encountering the tail risk.
Candidate B would have considered the nature of relational joins and maybe come up with some weird B tree-like structure for the underlying structure. Or maybe he would consider an append only log for the high read/join volume. It doesn't matter. The point here is that Candidate B has identified the characteristics of the system that are needed and prioritizes them. From there on, he can research the thousands of databases available to him and pick the one that aligns with the needs of the system.
Seriously, the number of proprietary NO-SQL databases in the world is so big it would make your head spin.
Candidate A would just be chasing tech stack clout and not really understand what his system needs. If you hired Candidate A, he would blow up your tech stack with shitty decisions.
Imagine hearing “I don’t know why it's slow! It's the database everyone is using” when you ask him why the performance of your product sucks. Is this the kind of guy you want to hire?
Nothing wrong with using an old tech stack if it solves your problem sufficiently and well. Tried and true fundamentals trump trends.
Remember what I said in the section about what is an engineer? You’re solving a problem. The first candidate is the equivalent to a trend-rider who can only evangelize the popular and potentially overrated. The second candidate is methodical and addresses real problems. All he needs to do is read up on some tech stacks which change as often as the seasons.
In other words, every component, every model, every choice we make is done in service of the problem or a subproblem. Which seems obvious but you know, some of yall need to hear it.
But the actions of the general engineering public run contrary to this. When everyone spams “node js, fusion, redis cache, mysql database” as the solution to every problem, you begin to question whether people are just rolling the same tech stack over and over again mindlessly or are actually trying to solve the problem.
Granted it DOES solve the problem. But it doesn’t solve the problem in a meaningful way. It's akin to building a house with cat feces. Yes, technically it's a house. Yes you can technically build every house on a block out of feces. And yes it has walls. But it stinks.
In short, what separates bad candidates and good candidates is just 1 thing: logic as it pertains to the problem at hand. But in that, there are a lot of meanings and we will discuss this further.
For now, consider that you can think of the golden solution as simply being able to justify your decisions as much as you can.
System design isn’t hard. It’s not unlike actually solving a LeetCode problem where every step you take is meant to address the inefficiencies in a solution. Where people fuck up is in trying to throw some predetermined tech stack at a problem and thinking it magically solves everything.
Those people have never tasted a catastrophic architectural failure from shitty decisions and don’t understand the gravity of their decisions. These are the last people you should ever ask to help run your system.
And yes, there are an infinite number of things to consider. The database, the software architecture, load balancers, sharding, etc. But if you understand them at a high level and can describe the arrangement/characteristics of the problem you need, you don’t even need to describe tech stacks at all or name any proprietary technology.
Tech comes and goes. So I hope you hire the right guy who can analyze and understand the right one to use. A company that doesn’t do that is a company I would short into the ground.
So let me repeat the two resources I recommend: Grokking the System Design and Designing Data Intensive Applications. Use these in tandem.
Many of the same ideas in the section on How To Study Algorithms can be applied for system design. Nothing really changes here. You start with a systematic approach and compare your answers to the optimal answer suggested and fix your mistakes.
The only difference is that you have a lot more variables thrown at you and its important. You can’t really just study databases in a vacuum: you need to actually apply it to really understand it.
The best way to actually improve at the system design is iteratively and continually fix mistakes.
Look at solutions provided by a good source of knowledge like Grokking the System Design and try to reverse engineer the thought process behind developing the system. Why did they pick the components they did? What was the intention at each step? What was the pain point they intended to deal with?
For any area that you don’t quite understand like sharding or synchronization, you should read the Designing Data Intensive Applications chapter for that. Understand every tradeoff, factor, and application listed in DDIA that went into the Grokking solution and try to reason why it was reasonable or not.
I personally find it meaningless to read a dense 500 page book without applying it and with my clients, I find it's very effective to consume maybe 10-15 pages at a time and really try to practice those pages.
For instance, if I do not understand why a load balancer would be placed at position A instead of position B, then I’ll go and read the chapter on it and apply it to the problem and solution I just saw. It's not so important that I agree or disagree with the answer. It's important that I understand or at least come up with a reason why the solution is to put a load balancer there given all the characteristics of a load balance and why it's there.
You need to repeat this process until you’ve fully understood a good majority of DDIA and can hold a meaningful conversation. If I wake you up in the middle of the night and ask you how asynchronous replication and synchronization works for various configurations of leader/leaderless/multi-leader databases, you should be able to tell me how they work. Otherwise, practice harder.
And be patient! This alone can easily take you 3+ months if you’re starting from scratch!
The general algorithm for doing so is not unlike solving a LeetCode style question and, by proxy, the engineering process. If you follow the philosophy of planning in logical succession the order in which you do things, you will notice a lot of similarities.
Let's dive into these steps
This is where you actually craft your MVP, your minimum viable product. This is what will cover your p90 case or so. Because no system is actually created perfectly or instantly off the bat, you need to start somewhere.
This is not unlike the code or engineering process itself. You start with a very naive solution with off the shelf components that will service a small set of users. You get your product up and running and then address the concerns you run into.
The reason why we do this is to scope out the main flow of our system and where we may get blown up. Where our problem areas will be. Where we should pay special attention. If this is the planning stage, consider creating a suite of tools that allow you to scan the entirety of the problem.
My personal favorite tools for this section are the CAP theorem, estimating metrics, and outlining how user interactions flow throughout the system. These are your starting clues, your initial scan of the system to see where you will choke and might run into issues. We want to find areas of high traffic or where the data flow is not solved by an obvious solution.
Because every system and design has their limitations and tradeoffs, we are forced to address them. And this is an exercise we will repeat over and over again not just across problems but within the same problem. As we scale up in users, resources, or some other activity like external vendors, our system will eventually start failing or having issues in one or more areas.
At this point, we will update those areas. For instance, if I need to scale my database, I might want to shard the data. If I am expanding geographically, I may consider replication. If I want to handle queries in a better way, I may want to change from a standard MySQL to a NoSql database.
But what drives these changes are a consideration of tradeoffs and how the system is being used.
We might know what we’d ideally like to do. The problem is that our solution needs to be relevant to the matter at hand and we would like to be as intelligent as possible about it.
If we decide to use one solution, along what dimension should we use it? For instance, we may want to cache some portion of the returned result. The question then becomes where this cache should live, how long the cached data should live, and what our caching policy should be. And all these factors and dimensions should be relevant to the end user’s behavior and how the system will be treated.
A big mistake people make here is that they think the system should be robust to just handle the initial problem and only the initial problem. What you really need to ask yourself is how the user is using your system and what reasonable behaviors they may have.
For instance, it does not make sense to have a caching policy where the TTL is for two weeks if we’re designing a instant messaging app as the cache constantly changes and people don’t care about messages from more than three days ago. But this might be correct if we are designing a news website like Reuters where people will look up recent news in the past few days.
Of course, not every optimization is going to work out of the gate. You may have some prior questions or blocking concerns preventing you from implementing the solution you want. In that case, go back and repeat the exercise for that blocking factor.
Its more or less an extension of trying to unstuck yourself out of a problem or situation when doing your data structures or algorithms. Work backwards and ask yourself what you need to know and what is preventing you from knowing it until you can arrive at a simple matter of fact or straight forward decision.
As you scale up, there are certain factors that will falter and fail inevitably. It might be multiple writes to a database. It might be server load and need to replicate across regions. As your audience scales, it necessitates these solutions.
Even at Facebook and Google’s scale where the fallibility of the processors themselves to fail an instruction execution properly is a bottleneck; a problem that literally no other company is handling. There will always be problems that need to be handled.
“But wait! I’d be revising my answer multiple multiple times in this case! There isn’t enough time to keep iterating! And I can’t possibly know everything!”
Oh, contraire. This is basically what the engineering process is. You will start with an assumption and that assumption will be wrong eventually. That’s inevitable because you aren’t a fucking god (if you were and your name isn’t Linus Torvalds, then you can just Dunning Kruger your way back to L3).
The fact of the matter is that for system design, a lot of the chain of decision making should be kinetic, much like patterns. In fact, at higher levels of conversation and between people of higher expertise, the conversation actually has a lot of implicit assumptions and goes by very quickly and any clarifications can be drilled down extremely deeply.
For instance, if I talk about returning data to the user that requires a complex set of queries, I can get away with mentioning offhand that the database is MySQL since there is a good chance that simply doing relational joins is going to give me better performance. I don’t need to run through the exercise of pain points since both parties already can already assume its going to give the better performance. But such assumptions and knowing what to skip and what decisions to actually dive deep into only come with experience.
The real question, like I said above, is in how you make those decisions. The branch points. The negotiations. If I ask you why a component is in place, you should be able to provide a logical answer based on factors and considerations, whether they are given to you or one you derive yourself.
The only real difference between a top tier engineer and an L4 at this stage is how quickly they can think about all these questions by prioritizing which decisions need to be made and already having some idea of the answer. This usually happens because they are very well experienced with it. This comes from experience but, as a general principle, the best decisions is almost always the one that will give you the most options later on.
Just like everything in life, nothing comes for free. You are in a balancing act of complexity, time to run, and size. And its not even that good to do the “here are 3 options, pick 2” meme.
If a system is massive and incredibly complex but runs fast, that system will die because its too difficult to change.
In a similar vein, are there NoSQL databases that can act like MySQL? Yes. But using it in the wrong situation will yield suboptimal results.
This is why being able to actually justify every decision is important. It shows your ability to plan ahead and accommodate for the unknown and the most likely scenario. Nobody can instantly know how to design a system from start to end. It is done by building pieces upon pieces slowly and surely and making sure those pieces are flexible enough to handle the problems that happen later on down the road. At the same time, they also perform optimally in our desired situations.
And this need to actually build things one piece at a time is why we have such an iterative process in engineering. And no where is this more apparent than in system design.
But enough high level philosophy. Let’s actually do an example.
Let’s do a problem to prove that the aforementioned strategy actually works.
For your convenience, here are video examples of me walking through the system design.
But here’s the text version for those who don’t want to watch a video. The problem we will solve is “Design a Url Shortening Service”. The numbers and situations I will use here will be taken from the Grokking the System Design for convenience.
Design the basic, high level, end to end.
User submits a url, we return back a hashed version of that url as the id of our domain url.
Ie. www.foo.com => tinyurl.com/abcde
When the user submits that url, we want to be able to redirect the user to the original website. This requires us to have a DB.
Immediately we have some limitations to the problems. We need to know a few metrics that require our attention.
What is our current pain point or unknown?
1) How many urls can we store?
We cannot possibly store every url in the universe. This can be a function of the size of the id space. Which also leads us to what hashing algorithm we use.
2) What is our TTL (a consequence of #1)
In order to make room for more urls, we should purge by some rule, whether that is age or popularity.
3) How many requests are we getting?
This is going to affect our read and writes and the server operations. We can even be more specific and split the read and write operations separately.
Any other metric at this point is (arguably) irrelevant. This is because we don’t know enough about the system just yet that we are building. As for additional features, they are built on top of this so we can acknowledge them now (ie. user controlled deletion) but are not core to the central feature.
This is also probably where a candidate might make the first mistake of naming a particular hashing algorithm. They may roll MD5 off the tongue. But, as mentioned before, this means you’re dismissing all the other hashing algorithms like sha256. If I asked why a candidate picked this algorithm and they couldn’t at least list off the differences between at least four hashing algorithms (time complexity, etc.) and say in detail how each worked, I would give that a red flag. Why declaring a hash at this point in time is a mistake, we will discuss later.
Also we have the issue of hashing collisions but we can address that later. For now, let’s get the bare bones on the table.
Let’s handwave some of the math and let's suppose you came up with (Note that the numbers are arbitrary)
1) Let’s keep it simple and keep it to 6 characters, alphanumeric, upper and lower. (2*26+10)^6 combinations = 56GB for the ids pool. Every url is also going to have to be stored as well. Approximate 500B = 15TB of url ids.
2) Let’s TTL by age of the URL.
3) 100:1 reads: writes. 500M urls shortened every month. So ~20k requests/second.
Now that we have the metrics out of the way, we know the scope and flow of data.
What is our current pain point or unknown?
The most prevalent pain point is the # of requests/sec. 15TB of data is not bad in comparison. A few trips to Best Buy will suffice.
This brings us to what the user can expect from us in terms of service. That is, what are the problems associated with servicing a high number of requests?
We expect the service to, at least, have high availability, redirection should be done with minimal latency, and the url should not be guessable.
With the guessable requirement, we may want to expand our ID pool and add two more dummy bytes. This would increase our size to 200 TB or so. This would be the point whether we want to ask the interviewer if this requirement is necessary, to discard it, or to dive deeper into our math and see if we even need the dummy bytes. But this is also a tangent that can be tabled.
But we still need to address the high availability and low latency. We cannot answer these currently because we need to be able to translate the url into its original url. And we cannot certainly keep 15TB in memory. This means we need a database.
What is preventing us from fully coming up with a solution for these requirements, if any?
The first two requirements (availability and redirection) both depend on what the request/response contract looks like and how they are processed. Are there any crazy processing/operations we need to support?
The requests to manipulate urls, at the bare basic level, should look like this
createUrl(url)
deleteUrl(hashed_url)
We can add more fancy options later like a custom_alias or a TTL. While deleting a URL is not necessarily part of the system, it is reasonably expected. A user should be able to delete a url if they own it. But let’s table that and come back to it.
So the requests look simple. There is nothing here that we need to consider as a blocking factor.
What is the current pain point?
Since the requests are relatively simple, the current pain point will be in the translation of the url to url. That is, lookup to the DB. Another angle to look at this is to see if we need to shard the DB as well as that can affect our solution.
If we can do this, we can also load balance the requests in some way. But this requires us to know how we are going to set up the servers and therefore, how we will set up our DB backend and sharding. Knowing this will allow us to choose the best policy for the LB.
What is preventing us from fully coming up with a solution for these requirements, if any?
We must answer the preceding issue: what does our DB look like?
So what is preventing us from concretely coming up with a DB? Our data models: what kind of relationship will they have? Let’s represent this first.
<id, original url, created time, expire time>
This is a suitable candidate for a MySQL DB. If we assume that we are receiving 20k read/write requests per second and fills our high availability and minimal latency.
We could also go for NoSQL as well but this discussion requires a comparison of alternatives. Again, don’t start naming databases unless you’re ready to have a deep dive discussion. But you can at least define the characteristics of a good database.
Now, if we do need to shard the database, we can do this either by evenly distributing the urls across databases. This can mean hashing the urls and sharding by alphanumeric groups.
Another potential alternative is that because some urls have higher traffic than others, we can try to even out the traffic based on distribution. That is, the highest traffic urls will have their own dedicated shard and the lowest traffic ones will be put on other shards.
For now, let’s assume the simpler solution: hashing the urls themselves that can resolve to a particular db shard. If we find that this solution does not work, we have another one in mind.
Going Back
Now that we have this, let’s go back to our original: the requests. The replicated servers aren’t going to be dedicated to a database shard. They could potentially be writing to the same db. So let’s just have a round robin to even out the load between servers.
What Is the Current Pain Point?
Now that we know how we’re going to be storing the urls and sending it back, the next pain point is when we have enough traffic that our hashing may create collisions.
We could keep running the hash algorithm over and over, salting the url until there are no more collisions but this could theoretically continue ad infinitum. There must be a better solution.
Let’s use a key pool instead. We will instead generate keys and create a pool of available keys as ids. The server will withdraw from the pool and every time a url gets deleted per the TTL, that key will be returned to the pool.
Now there are some issues with that. After all, if this pool contains all the keys that are used and unused, we need to be very strict. That read and write should be maintained by the same lock. After all, if a key is returned to the pool, it can be available for another server to use.
The pool will be limited in its capacity so as to not demand more keys than necessary. It will keep some of the keys in memory and then keep the rest on disc. We can do some math here to determine the amount but for our purposes, we can leave that detail out for now.
With all this said and done, we can now formally identify the error scenario. If a hashed url is called and the url is expired, we send the user a 404 page.
With that, have we satisfied our requirements for have high availability, redirection should be done with minimal latency?
Arguably yes. But we can do a little better. We can make the argument that caching should be involved in order to reduce the latency time as well so that we don’t keep connecting to the database.
Caching
Let’s use that idea about the most popular urls but instead of for sharding, we use it for urls. We can have an in-memory cache that is akin to a hashmap and use a LRU eviction policy. As urls become less popular, they get evicted from the cache.
When a url gets invalidated from the cache, we invalidate it in the DB and trigger returning that key to the pool. This does lead to some weird issues where the key could be immediately used again and redirect to a new url (leading to confusion with the existing url). But that’s a smaller problem by balance of probability and we have bigger fish to fry.
Finishing off the requirements: TTL
With the basics set up, let’s tackle the cleanup. The last requirement.
Every time we receive a request that results in a expired url being retrieved, We can also purge the url at the same time and send the key back to the pool. Ontop of this, we can periodically run a service to query the database for expired keys.
Bonus: User Control of URLs
If we want users to be able to delete urls at will, we need to expand our table to be
<id, userId, original url, created time, expire time>
And then we open the discussion for user login, management of urls, authentication, etc.
<userid, email, creationDate, pwhash, etc..>
This is outside of the scope of the core functionality but this is worth at least mentioning.
We have pretty much arrived at a similar answer as the Grokking folks (albeit with some help from the math department) purely through logic. And all we did was just keep unblocking ourselves as we tried to resolve the requirements of the system in a bare bones way, acknowledging which were priorities and which were just bells and whistles to the core product.
This is just a simpler way of saying “what are the functional requirements and non functional requirements”.
Notice here that at no point, do we commit ourselves to a specific technology until we know for sure it fits our needs. Rather, we identify characteristics of the system/component or even algorithm that we want. We make no commitment to it until we’ve sufficiently reasoned that our system can achieve a stable state in its current form, provided it fits the mold and our requirements.
Let’s use our hashing as an example. In the very beginning, we believed that we would use a simple hashing algorithm. A noob would just jump into “what hashing algorithm do we use” instead of seeing the bigger picture, namely “are we even going to use the hashing algorithm in this way.” Choosing the algorithm does not give us more options in choosing our system components or reveal any further insights into what it needs to do. At best, it distracts us. At worst, it constrains us.
It is only when we reason that key collision may have an adverse effect on our system that we invent our key pool. At that point, hashing algorithms almost become irrelevant. So whoever started off the system design with a discussion on MD5 vs SHA1 failed the interview.
Secondly, calculations and estimations serve as a litmus test for the possible pain points you will encounter. A system that requires 200TB and serves 10 people is going to be less painful and less tedious than a system that requires 200TB and serve 10M people. That difference in the number of people served will require you to have data server replication, load balancing, etc. It changes what you need to think about.
Another thing to note is that every single decision I make is a consequence of the previous decision. That a decision opens itself to questions and observations that immediately need to be discovered and resolved.
Finally, the entire process was just a systematic, routine effort of “Let’s do the basics first and then go back to add some more stuff and unblock ourselves.”
I thought this would actually be a very interesting section to add. As mentioned before, with the trend more flexible and relevant work, less people are asking about engineering a actual end-to-end system and more about designing products and architecture, harnessing an already existing system.
Here is an example:
Suppose you are in 2012. Facebook wants to internationalize its infrastructure. How would you do this?
This is a more theoretical discussion as I have not actually tested at a L6 level myself. The only way I can speak to this is by drawing upon my own work interactions with staff engineers at both Uber and Apple.
But I firmly believe that if you are a L6 level, you actually don’t do things too differently. Instead you just end up analyzing and justifying systems even better by deep diving and pulling back the curtain on a good number of these tech stacks. You would be able to state how the data structures and system organization driving these tech stacks at a fundamental level would work.
For instance, if you were talking about databases, then a staff engineer should be comfortable talking about how B trees work in relation to a database and say what happens when things get added and taken away off the top of his head. In fact, his line of work and title would suggest that he has read the source code for a number of databases as well and can have a relatively intelligent conversation about the underlying implementations and probably has written something modifying them as well.
The approach is probably very similar. If the question is a matter of how deep someone can get into the details, then the setup to that situation should not be any different.
Believe it or not, there IS a RIGHT way and a WRONG way to answer these questions beyond giving a politically correct answer. There is a hidden technical aspect to this portion.
I realized in the previous edition that this section was fairly lackluster compared to my other responses. I did admittedly say that its hard to fail the behavioral interview because I kind of figured that people weren’t stupid enough to walk into the behavioral interview unprepared. If you prepare well and know what is going to happen ahead of time, you can get upleveled.
But I guess I overestimated the crowd. So I’m going to actually have to give you guys a step-by-step to prepare for the behavioral interview.
A lot of behavioral interview prep out there is pretty half-baked because the response can vary so widely that there is no consistent course/method of evaluation to fine-tune a response. A basic non-technical interview practice coach is probably fine if you want to get comfortable talking about your projects. But it can quickly spin into a technical discussion about a project on your resume and I don’t think anyone other than another senior engineer can tell you whether you gave the right or wrong answer.
If that’s the case, then what the heck are people looking for? If it was that easy and that nebulous, then simply not being a douchebag would be enough, right?
Except it isn’t that easy. The expectations of you as an engineer at different levels vary greatly and we expect a certain type of person who can understand their projects at different depths of understanding.
The short version is that if someone tells you that they are a professional sushi chef, you would expect them to have an in-depth discussion on the varying types of tuna found in oceans around the world, the different cuts of tuna, how to prepare and source them, and so on.
If you’re talking to a home chef who happens to make sushi once in a while, you’d probably just ask what supermarket they get their fish from.
Different levels of expertise command different levels of discussion. At each corner, you are expected to defend your answer and provide an appropriate level of consideration and response.
In the software engineering role, the projects you claim you do are the impetus for these discussions. You need to talk about not only what you did, but why you did it and the decisions and considerations you made along the way.
Ultimately, your job consists of fulfilling responsibilities. The purpose of the behavioral interview is to figure out how much responsibility you can handle. We call this leveling. Being entrusted with that level of responsibility requires a good understanding of details and implementations. How code can be affected if we write things one way versus another. How code can be affected if we let business development steamroll the developers.
And we want to make sure that the person who we’re hiring has acted and does tasks in a way befitting of those responsibilities and expectations. We want to see moments and times when you’ve performed in a manner that’s consistent with that.
We take the responsibility as a sign of that understanding. And knowing all these dimensions and all these tradeoffs is what makes a software engineer good. Your job in the interview is to show you can actually think about these issues and you are competent enough to understand what your role is as an engineer. And based on those answers, we will judge your ability to take on responsibilities of certain levels.
The more skilled you are, the more likely you are to make the right choice and the more leverage you are trusted with to make sure that the code and project heads in the right direction.
Oh yes, how do we pick what we want to talk about? At a bare minimum, you should probably have a story for all your jobs and projects that you have listed on your resume. Anything you submitted is fair game. But most likely, the talks will be limited to what you did in the past 5-7 years.
What we really want isn’t just the surface level of what you did during your job but also the details. The talking points. The thing that makes your job go from “boring corporate slave” to “holy shit, that’s sexy as fuck.”
Because we want to hire a programmer who can write sexy projects, not just be a drone who mindlessly shovels out code. Look for moments and projects where you can look back and say, “Oh damn, I did that. That was fucking cool and I’m proud of that!” Chances are, someone else will have that reaction. Look for times where you delivered an actual impact or took autonomy and control beyond your expected duties. These can be small moments during a project or large overall decision and discussions that lasted for months.
You should have at least 5-7 well-rehearsed talking points and stories like these. Make these topics and stories malleable and multi-faceted so that they can be morphed into whatever answer you need.
Now that you have chosen your stories, each one should have a 30 second, 1-minute, and 2 minute version. But you really only need to memorize the 30 second version.
Why, then, do we need the 1-minute and 2-minute version? Because we want to mine all the details out of the experience. What people usually mess up on is rambling. They don’t talk objectively. They insert a bunch of unnecessary details that bore the interviewer (who the fuck cares if you wrote some code that was buggy and you debugged it? Why is that part of your story?). There needs to always be some “Aha!” moments in the story that keep people interested and make them want to dive deeper.
Doing that exercise will help you narrow in on the details that actually matter. I would recommend you write it out on paper and read it. If you are having trouble trying to figure out what details matter, then compare your story to the expectations to your level and pull some traits from your level+1 on the Square Engineering Ladder. Make sure your story shows at least 1-2 characteristics from these.
This is a rubric that Square uses to evaluate the quality of their engineers, but the basics are still pretty similar at every company across the board. Any company will at least have these expectations. I use the level+1 simply to ensure that we cover our bases and more. It is good to show some signs of brilliance. But ultimately, we still want to ensure we make no mistakes and sit very solidly at our level.
Because on the surface, the path forward is obvious: show you do the work expected of a candidate at x level and get an offer for x level.
But there’s actually a few caveats to this.
For one, the rubrics tend to be a little vague and you can see here in the square engineering ladder that statements like:
“• Implements code that is clear, concise, tested, and easily understood by others”
Seems like a requirement of even a L3 engineer. Why are some attributes in one category but not another.
Furthermore, if you want to try talking as if you’re a senior engineer and you spend all your time talking about how you managed some factory or facade pattern in the code as your contribution, then that sounds more like a L3 level role. This is despite the fact that even L5 senior engineers are expected to code.
So make sure that you can tie back that detail to some expectation that is at your desired level.
When you present these stories, the important question is why you did something. The reasoning, the steps, the thought considerations, and so on. Its not just the scope of the project but the way you evaluate and think about these projects.
So we’re going to poke holes at it by asking you what the circumstances were, why you made your decisions what steps you took to come to the conclusions that you did, and any vague holes in your story that aren’t clear.
You need to anticipate these. Fortunately, if you follow the expectations ladder, you have a very good idea of where these questions might come from. For instance, if you are applying as an L4 engineer we can see one of the expectations is:
• Responsible for the entire lifecycle of their code: development, test, production, and subsequent fixes and improvements.
So if you talk about writing a product, the most obvious questions would be whether the project was your own initiative or something that someone told you to write. If you came up with the testing. What problems did you consider or anticipate in the project. Were there any requests from business development that you needed changed?
All these are questions that an experienced L4 engineer would be able to understand. After all, when writing self-sufficient code, you probably will encounter many instances of these (where you need to write the tests, where expectations change and you need to pivot, and so on).
So make sure you have a detail or two that can cover as many of these as possible.
L3: I was working on fixing a bug. Me and my coworker disagreed on how to actually fix this. I proposed writing a new class and moving the code to that class to encapsulate the feature or using a dependency inversion since the class was probably going to be replaced at some point. He wanted to refactor the code entirely and rewrite it. In the end, we decided that the impact of the feature was not big enough to warrant a rewrite and we decided to move the code.
Engineering Ladder: • Writes, tests, and documents code according to Square Engineering standards and practices. • Participates in software design for features and bug fixes under direct supervision.
This is a fine answer. However, the topic of interest is really the code writing. People have lots of disagreements about code anyway. But if that situation is the first thing you think of, then chances are its representative of your day-to-day responsibilities. Fussing over details in code tends to be junior-level work.
But the junior engineer does get bonus points for knowing these coding principles. At this point, it becomes a technical discussion and I would quiz him on what a dependency inversion is and why it may be useful.
L4: I wanted to write a service that would allow for automatic retrieval of a list of known bad actors and IPs to block malicious services. My lead disagreed on how often the job should be run. We decided to parameterize the frequency of retrieval. We would start at a low frequency and raise it as we saw fit over time.
In the same vein, this one covers the scope of having to write a full-on independent unit of work on your own. The interaction with the lead is most likely the only collaborative interaction that he has. If it was an L3, then chances are he would be hand held—he would require a lead constantly looking over his shoulder to make sure he doesn’t veer off track. Working with a technical lead to do some self-sufficient unit of work is L4 work.
Engineering Ladder: • • Designs, develops, ships, and maintains features with guidance from more experienced engineers. • Is responsible for the entire lifecycle of their code: development, test, production, and subsequent fixes and improvements.
This conflict is very reasonable as well. I would follow-up by asking what the lead’s reasoning is for disagreeing. If the candidate cannot say, then chances are he is not paying good attention to detail and I would downlevel him. However, if the candidate can show a good thoughtful explanation of the tradeoffs, then he is solidly a L4.
L5: I lead a feature that required the involvement of both the ios and backend engineer. We disagreed on the backend/frontend contract and interaction. We had to get our respective staff engineers involved since this was a systematic problem and question that was happening on every feature. If a solution seems too complex for a simple problem, then chances are there are much bigger issues at hand and its best to ask people who know.
Going back to before, the responsibility now is to lead a small team to complete a three month project. He is also talking about reconciling issues between subprojects. This is squarely within the L5 responsibilities.
Engineering Ladder: •• Is highly proficient in one or more technical areas. • [Shared] Works with their team and adjacent teams to solve problems. Escalates problems that have wider scope.
What I like about this answer is that it is a mindful attention to systematic issues rather than just trying to ship product out the door. I don’t think I would knock any points off for not knowing how to address systematic issues. There is nothing wrong with asking people who know more than you and have better exposure than you to validate a question or an issue. It can show a good amount of care, especially if its out of your depth.
But suppose the engineer did not actually know or understand why the issue was systematic after asking. To me, that demonstrates carelessness and an “I don’t give a shit” attitude. I would not even entertain the idea of downleveling: I’d simply reject the candidate. I don’t want shitty coders who do not give a crap about what’s going on in the ecosystem that they’re writing in to be my coworkers.
Now you might be wondering what a L5+ answer would look like:
L5+: I was working on migrating our entire codebase to Kotlin and me and my coworker disagreed on how much risk should be assumed during this migration. I hedged against the risk by putting flags and reverts in place silently to not disrupt the developer experience and that any disruptions could be immediately fixed. My coworker wanted to fully validate everything from the code to binary size at each step manually before pushing a change out. In the end, we incorporated our ideas together and automated the build and validation into a shadow pipeline to warn us daily of any issues.
Engineering Ladder: • Demonstrates ability to succeed in a wide range of complex situations across multiple axes: e.g. scale, uncertainty, interconnectedness. ••• Estimates and manages project timelines and risk. • Leads or significantly contributes to medium-to-large feature releases; usually multi-person projects that cross engineering team boundaries.
This one is a lot harder to explain because there are so many layers. On one hand, the size of the code definitely seems L5 or L5+. On the other hand, if the migration is simple, it could be an L5 level of work.
At some level of scale, hedging risk because of the code size, all the things that can blow you up, and being entrusted to handle a large code migration that can affect hundreds of developers is a very big responsibility you don’t hand to just anyone.
I would probably be apprehensive about hiring this person as a L5+. I would like to have him actually be able to explain more about why he thought flags were enough of a hedge and what convinced him to take even more precautions. There are only so many things you can protect yourself against without dragging out the project.
Given those answers and if they are reasonable or not, this would be a good L5+ answer.
Here’s something interesting:
It really does not matter what question is being asked. It can be to describe a weakness. It can be to describe a time when you did not work well with a teammate. What the question is asking is irrelevant.
What IS relevant is the philosophy and bigger point about what you value in a project and your job.
Software engineering is hard. And I mean hard. You are combining a bunch of heuristics and rules that are not systematically ordered to try and create a system that millions of people rely on. We are essentially cobbling together parts using past patterns we’ve encountered and that we think will work based on feeling. In fact, if we built bridges the same way we built software, everyone working on the project would be executed for incompetence.
To manage all of this properly, you need to understand what your job entails. You need to understand how multiple different factors affect your code ranging from upper management to design patterns to the very line you write.
Let’s take a hypothetical example. Suppose you are asked to write a new feature. However, doing so requires you to rip the guts out of the old codebase to do so. How do you handle this? You could refactor the codebase to be more accommodating for both the old and new features which would take a lot of time you probably don’t have. You could fight to not do the new feature which would expend the political capital of your group. You could just force it in but then your codebase would be a mess and you might have to spend time later cleaning it up or, worst case, force a rewrite.
Knowing all these dimensions and tradeoffs is what makes a software engineer good. Your job in the interview is to show you can actually think about these issues and you are competent enough to understand what your role is as an engineer.
In short, your job as an engineer is to understand and protect your code and system.
What does this mean? It means a lot of different things (including balancing “good enough” vs “perfect” code) and talking about it goes beyond the scope of this guide. But the more you understand and care for the code you’re working on, the more you will understand what it means.
At any rate, all questions and stories you have should always lead to this principle or at least a principle regarding your belief in how code should be organized/handled.
You did not get along with your coworker for a project? Maybe talk about how you did not see eye-to-eye for the future of the codebase and you resolved it by making the architecture flexible enough to accommodate for future plans. Your biggest weakness? Getting bored when new features aren’t challenging to implement because it means the codebase you protect is clean (humble brag).
No matter what answer you give, always, always, always tie it back to how committed you are to the quality and protection of your code. It doesn’t have to be directly. It can be a bit roundabout (ie. team cohesion). But if you cannot eventually tie it to the quality of your code, you are giving a bad answer!
No company is going to value a guy who cannot think about the quality of their code or its future impact. At best, they’re just going to be fixing bugs all day. So always try to eventually pivot to your core values after answering the question.
Believe it or not, the section at the end of the interview where the interviewer asks you “do you have any questions for me?” is actually important. This is your chance to make the interviewer see you in a different light. However, this is only relevant if you actually passed or are borderline on the technical portion because even if you ask the right questions in this portion but failed your technical, it means nothing.
The questions you ask will show the interviewer what you prioritize in your professional career and therefore, your level. As you are promoted, you assume more responsibilities and you must be able to act in a manner befitting of those responsibilities.
If you’re asking about the culture, work-life balance, the environment, core values, switching teams, etc., chances you are a candidate is prioritizing the comfort of the work environment which means you are probably going to think like a typical salaried employee. This is very common for L3 or L4. That is perfectly ok. Maybe that is what you prioritize because you have a kid and work isn’t your #1 priority. You can have that and still be very competent.
But most engineers, including your interviewer, don’t pay attention to the company’s “core values” or memorize them or really pay attention to the company perks—they’re focused on trying to fix the damn regression that QA found two days ago and they have to fix it in the next four hours.
However, if you’re asking questions about the codebase, product cycle, releases, migrations to new languages, how new features get approved, etc., then it's a different ballgame. Engineers who are looking to play a bigger role and expand their presence and skill have a natural inclination to ask these questions because they understand how each of these elements affects their work. They are more valuable to the company because it is more cost-effective to grow talent than to hire new talent and spend six months ramping them up on a codebase before they contribute anything meaningful.
Prioritization of features over code quality can lead to expensive rewrites down the road which means a delay of future features. Migrations to new languages require teaching employees the new language and also require an investigation/experiment into how the new language can affect compilation times and the compiled code size. Issues like these will affect the end user because nobody wants to download an app that has grown from 10mb to 60mb with no significant new features.
To show you are the kind of engineer who seriously thinks of issues like this, an example question would be, “How do you split your time between new features and refactoring old code? Do you try to spend a chunk of time every cycle dedicated to code cleanup? Or are these dedicated tasks in particular cycles?”
This question shows that you are aware of the dangers of bad code and are thinking about how the company management prioritizes preventing this. It can spin naturally into a conversation about the culture of the company and its attitude towards its own products and codebase. It can lead to a discussion about how project proposals are done (ex: are they grown from the developers or do they come from the top down?).
These are concerns you really only have once you have suffered through multiple scenarios and different prioritizations where those concerns were ignored. So the best way to do this is to really acquire meaningful experience where more senior engineers can tell you how and why different issues occur. However, there are books where you can actually get to this point a lot faster. If you can touch on the topics in the last 5 minutes, you will appear to be far more experienced than your resume may let on.
It is very easy to come up with these questions if you have the experience and understanding of how products and projects can fail because of management/code. The trick is knowing that you need to ask them and what questions you ask can push you into a higher offer.
How To Improve At Behavioral Interviews
Compared to the technical portion, the behavioral interview is actually very easy to improve on. How?
Here’s a novel concept: how about actually gitting gud your job? (LOOK AT THAT, GIT GUD). Or at least what it takes to make sure you can do your job much better than you did yesterday. Learn what it takes to get a proposal from start to finish. Learn how different patterns, techniques, and philosophies affect a codebase and its future. Understand how these play into discussions with your coworkers so you can actually participate in feature planning and bug reviews.
In turn, you can actually have a meaningful discussion about your code, the future of the project, and therefore be more competent in leading the future of a project which is what is expected of a senior engineer. If you have demonstrated that very potential and effort, you’ll probably be given a higher offer and an easier path to promotion.
Software engineering is more than just lines of code. It’s a craft that takes years to get good at and what approaches you take to your craft depends on your professional environment. Your high salary doesn’t mean anything if you can’t be better than a new grad. It all starts with taking responsibility for the future of the code that not only you write, but your colleagues write.
Recommended Resources
I would recommend Clean Code, Clean Architecture, Pragmatic Programmer, and your more senior colleagues. Not only should you read these for your interview (as a bonus after you finished your technical interview preparation) but you should also actively apply these concepts to your code as well in your everyday professional coding. You will feel like utter shit for six months trying these new ideas at your work. But if you can at least explain to more senior engineers that you are trying to better yourself by applying something you read, they can probably fix and tweak your understanding. You will progress faster in your career for having done that because you will build a rapport with them for being focused on quality. You will improve the quality of your merge requests and you will build trust with your coworkers.
For those who want explicit book recommendations in a video format, here is one for career and coding and here is one for learning from people’s merge requests. These recommendations are not likely to change as the fundamentals are very solid.
This section purely exists because most people believe that when they fail an interview, it's because they failed on the behavioral interview or some crazy, weird unrelated factor. They blame everything other than their own technical ability.
If people who couldn’t consistently get results would just kindly shut up, this section wouldn’t exist. Because there are a lot of misconceptions and myths.
I think this is a pretty pointless and irrelevant question when it comes to the technical interview. The interviewer is looking for a good engineer. Plain and simple.
But for real, I think people are asking this question to try and narrow the topics they need to study. Maybe looking for a way to skip dynamic programming or some topic they don’t understand. Which is really fucking stupid in my opinion.
No matter who you are, your job is to be the best you can be at the interview, not to game the interview by only studying specific questions.
Gaming the interview is probably the stupidest way to try and pass it because you’re basically guessing what will be asked. This means you will be screwed if something is outside your expectations. You’d rather be over-prepared than underprepared.
Everyone says “the interviewer cares about how you think.” That’s semi-true. To be honest, I think the way that phrase is being used is just feel-good jargon to motivate you to keep trying if you get stuck. But it only captures half the equation because not only do you need to have the right thinking, you need to get the right answer.
At the end of the day, if you get the right solution with little or minimal help, you’re fine. Interviewers will drop hints if and when you need them but if you can’t run far enough with those hints, they will fail you.
Basically, just fucking do well and keep trying. You’re in that room for 40 minutes anyway. Might as well use as much time as you can and push as hard as you can.
I will say that if you ace your technicals but still get rejected, 90% of the time the factors are completely not your fault and these situations are few and far between. At that point, you’ve done everything you can and it still wasn’t enough so don’t beat yourself up over it.
The other 10%? You’re “that guy”. You know, the guy who nobody can work with. The guy who can’t take feedback. The guy everyone dislikes. Nothing in this guide is going to fix that because you have bigger issues.
At Apple, you tend to interview with the team you’re going to work for so there is both a character matching aspect during the technical interview (behavioral to an extent, even if you’re not directly asked behavioral questions). Some teams are looking for a L5 dev but will settle for an L4. Other teams are strict about their requirements. Some people might want a talker, other people might not. Each team is different and wants a different combination of skill and personality.
But Apple is probably the exception rather than the rule for large companies. Facebook, Uber, and Google make you apply to the company as a whole and then team match you with whoever is hiring. So realistically speaking, at that point, it’s just simply doing well on your technical.
What you’re referring to is when the interviewer asks you to talk about yourself for the first five minutes to see if they will pass or fail you. They listen to your work history and decide whether you are worthy or not of their help during the technical portion. This is known to happen but I think it's far less likely to happen than you think. In fact, sometimes the interviewer just doesn’t ask you to talk about yourself.
I think this aspect is completely misunderstood. Why? It's kind of like when you’re talking to a person. Their mannerisms or what they talk about either make them likable or piss you off. That human element is a factor for sure. But at the same time, that isn’t what you should worry about.
Whether the interviewer helps you or not is a lifeline and can make or break you in your moments of weakness. But your goal is to be in a position to never rely on this. You should be good enough that you should never really rely on a lifeline or the interviewer’s help. Definitely, if you have some spare time, you should refine your professional persona to be more likable. But don’t worry too much until you’ve finished your technical interview study.
Being likable has nothing to do with whether or not you have the skill to solve the problem itself. It can tilt the table about 5% in either direction. But you should be so good that the interviewer has no choice but to acknowledge you.
The biggest red flag I see is when someone’s CoderPad’s code fails, they immediately reach for the debugger or logging statements. That’s the sign of a garbage-code generating monkey. They don’t spend the time to really read and understand their own code and don’t spend the time to figure out how to prevent writing these mistakes in the future.
This is an incredibly big problem in a real coding environment. It means the developer is not thinking about the structure of their code, much less how much impact that code has on the architecture or future development. After all, you cannot think of the bigger picture or how other code works with your code if you don't even know where your code is heading or what you’re doing. That kind of coding approach is just sloppy, irresponsible, and anyone in this habit should not be allowed to touch more sensitive or critical material.
If you do this and continue to do this, you will be replaced in 2-3 years. You might make L4…barely, but you’ll never be trusted enough to get to L5.
I would fail you immediately if you ever did this, even as an intern or L3 candidate. So don’t you dare do this during your CoderPad phone screens. Spend at least a few minutes reading over your own code and try to debug this without touching the keyboard.
Another red flag is when the candidate ignores help or fails to listen. I’ve been the guy who has tried to help a senior candidate get an answer only for them to go “yeah, uh-huh, yeah” and go off on their own while I’m talking and they still get nowhere. Dude, just shut up and let me try to help you ace this interview.
You know what the least-bright red flag is? Saying “I don’t know” or “I’m sorry maybe I don’t quite understand what you’re asking”. That’s actually ok. Nobody knows everything. Sometimes things are confusing. Maybe you have indeed misunderstood what was being asked. What you should do is show you’ve made an effort and gone down every single rabbit hole you have found. Chances are there is something very simple you’ve overlooked and if I nudged you in that direction and you took off and finished the problem, I wouldn’t care. Coding is a collaborative craft and nobody has all the answers. But if you can go far with just a small hint, that’s already a very good sign.
Saying “I don’t know” over and over again is a big fail though. In which case, yes, you do suck.
But you should study like you need to be the guy who knows everything.
“Signaling” is a secondary requirement. It is the bullshit icing on the cake of “get the optimal solution first.” Let’s look at Gayle McDowell’s answer (author of CTCI and now consult to many different companies about hiring the best engineers) to this particular Quora question about when a candidate answers a question suboptimally.
In all three of the hypothetical situations, she always mentions that the candidate has an optimal solution, even if they start out with a suboptimal solution. What you should also notice is that if you never get to the optimal solution, then you have no chance of getting hired! You can signal all you want but at the end of the day, even though she says that “It's always about signal”, it's ALWAYS about the right answer and the steps you take to get there.
Should it be about signaling? Yes. Are interviews about signaling in reality? No. Google has thousands of other candidates so they can pick whoever they want so they get the luxury of choosing candidates who signal AND get the optimal answer.
If you never get the right answer but you signal correctly, you will never get hired. On the flip side, if you get the optimal answer but you don’t signal correctly, you can get underhired or be asked for a follow-up interview. Once you get your foot in the door, it will usually take a year to get calibrated to your actual level. For most people, getting through the door is the hardest part.
So not only do you need the optimal solution, you should understand the reasoning as to what characteristics lead from a suboptimal solution to an optimal solution (which is exactly what I’m recommending with the characteristics identification).
In short, start with the suboptimal solution, point out the characteristics you can leverage to come up with an optimal solution, and code your optimal solution. Which is exactly what I’m telling you to do. But always, always, always prioritize showing the optimal solution and the steps to optimization secondary.
20% of the material you study will be relevant to 80% of the interview questions and vice versa. That being said, there are a lot of small things you should know as well, not just how to manipulate data structures.
While any common topic is usually worth knowing, it may not be worth your time to deep dive and master that topic if it isn’t likely to help you solve your interview questions.
Bigger companies tend to be more standard and just ask whiteboard questions. For mid-sized companies and some startups (Dropbox, Airbnb, etc.), knowing these non-LeetCode questions is fair game and more common.
You should be well aware of how your standard libraries and how Collections work underneath the hood. If you’re a Java programmer, you should understand how hashCode() and equals() works in conjunction with your HashMaps and so on. You should have some understanding and know how to work through the multi-threading problems (dining philosophers, producer/consumer, etc.). AVL trees, A* search, Red-Black trees, Huffman encoding, etc. You don’t have to know it hardcore as if it were for an exam; a cursory familiarity is good enough.
These are more conceptual questions, but they shouldn’t be your main focus since you’ll probably see them only 20% of the time, despite how broad the topics can be.
The most important out of all of these are questions about the programming language. If you are having a hard time understanding/remembering these programming language specific questions, my suggestion is to get more experience with side projects where ignoring these actually bites you in the ass.
For these specialized data structures, you should understand them but you won’t necessarily be asked to implement them. You will probably never be asked to actually implement an AVL tree. But some (like tries) will appear so you should be familiar with these structures to the point where you can actively think about how to implement them when asked (between cognitive and associative competency).
Try to focus on the things that help you solve the problems you’re more likely to encounter faster.
So you’ve committed yourself to actually getting good. This is not a very easy investment to make and is essentially a part-time, if not full time, job. If you are dedicated, you can easily get to autonomous competency in a month to six weeks.
I’m not going over how to make time for your studying. That’s up to you. You don’t have time? Then make time. I don’t care what it takes, you figure it out.
Here was my study schedule when I was practicing for interviews. When I’m not studying for interviews, I’m spending the study time reading or doing something similar. This schedule gives me 4.5 hours per weekday and 12 hours per weekend. I’m only asking you to do half of this.
Weekday | Weekend | |
0:00 | Study | |
0:30 | ||
1:00 | Sleep | |
1:30 | ||
2:00 | ||
2:30 | ||
3:00 | ||
3:30 | ||
4:00 | ||
4:30 | ||
5:00 | ||
5:30 | ||
6:00 | ||
6:30 | ||
7:00 | ||
7:30 | ||
8:00 | ||
8:30 | ||
9:00 | ||
9:30 | Work | Study |
10:00 | ||
10:30 | ||
11:00 | Break | |
11:30 | Study | |
12:00 | ||
12:30 | ||
13:00 | Lunch | |
13:30 | Study | |
14:00 | ||
14:30 | ||
15:00 | Break | |
15:30 | Study | |
16:00 | ||
16:30 | ||
17:00 | Break | |
17:30 | Study | |
18:00 | Gym | |
18:30 | Gym | |
19:00 | Dinner | |
19:30 | Study | |
20:00 | ||
20:30 | ||
21:00 | Break | |
21:30 | Study | |
22:00 | ||
22:30 | ||
23:00 | Break | |
23:30 | Study |
In my experience, it should take about 80-100 hours of prep to be good at these questions. The standard for “good” is to be able to answer every question and get to the optimal solution almost off the bat at least 50% of the time.
By the end of this, you should be at a point where you don’t have to think through the problem and you go on autopilot to solve a majority of the questions. There are some questions where no magic pattern/algorithm/data structure will save you (ie. rotate a 2D array) which you will have to hand parse and do on your own. But those aren’t the ones you should specifically prepare for because at that point it’s about your ability to break down the problem step-by-step and translate that idea to code.
You should already have this if you haven’t been cheating on every single problem in your practice.
At the end of every week, you should have finished about four sections of Grokking or three chapters of EPI. When I say complete, I mean you’re actually able to solve each question fluidly. This can mean repeating the sections and questions over and over again until you can get the answer or “trick” simply by looking at it. You will probably want to barf and you might get very angry at yourself as to why you’re not faster. Good, embrace it. It's going to be your new best friend for the next few weeks.
Do not obsess if you do not hit these benchmarks. I would say this section is practically worthless to most people outside of curiosity. It took me two hours, on average, to get good at each particular algorithm and about five hours for a data structure. I suspect it’s because I had a natural intuition for a lot of these topics anyway.
Do not obsess about hitting these benchmarks. These are just checkpoints to see if you are progressing in your studying and whether you need help or not. If it takes you longer, then it takes you longer. Don’t force it! You will know when you are fluent and ready!
Hours 1-35: Grokking the Coding Interview: Patterns for Coding Questions (omit DP section, we will do that later). I recommend doing every single problem except for the challenges (not necessary). Devote at least 20 min to each problem before you look at the answer. After every problem, try to redo them from the ground up. Focus on a systematic approach to the problems. It might seem like a lot of initial investment of time in the first few problems, but since the problems test the same technique, if you know how to practice right, you will be very fast at applying them over and over again so invest your time to doing the first problem RIGHT. If you do the first problem in the right way and think in the right way, the rest of the problems become a five-minute exercise.
Hours 35-50: Grokking Dynamic Programming Patterns for Coding Interviews (separate course). This was the hardest for me to start but once I got going, it was fine. There is a systematic way to approach all these problems except for the substring/subsequence ones. I just memorized those.
Hours 50-85: Elements of Programming Interview (EPI). Do the problem set for the "project" turn around time (three weeks). Personally, I did pseudo code for every question; I didn’t even write out the code because it was just far too time consuming and there wasn’t a way to check other than to actually write the test cases which took away time from actually studying.
For the recommended questions, I spent an average of 20 minutes trying to figure those out. For the non-recommended questions, I spent an average of 10. You can omit dynamic programming chapter. You really only need to cover chapters 5-16 for technical material. The first four chapters are introductory.
Hours 85-115: System Design study (omit if you’re aiming for L3). This one is particularly murky for me since I’m a mobile developer and most of this time was me re-reading Android documentation, system design patterns, what is new with Android versions, new components available, etc. I think Grokking System Design is good for most people who are going to be working backend or are full stack engineers. I bought the course, but it wasn’t particularly relevant to me.
Hours 115-130: LeetCode. Fill in the gap. Do mostly medium-level problems but also try random hard-level problems. These will tell you where you're missing knowledge. I recommend sticking with the questions from the section for company interviews. This is your “ramp down” period.
Note:
You will feel like absolute shit for the first 50 hours and you should. But you'll get over it. Briefly re-skim through the problems and see if you remember how to solve them before moving on to the next chapter.
Also remember to spend a good day or two before your on-site interview to wind down and not focus so much on studying. Otherwise, you will go crazy and get paranoid about what information you may or may not have forgotten.
If you don’t have as much time as I did, you can just do the Grokking series before your phone screen (without the DP portion) and finish up the rest prior to your on-site. If you find there is not enough time between now and your on-site, you can cut back on some of the EPI topics. Focus on arrays, lists, sorting, trees, graphs in Grokking and EPI. The bitwise section can be put for later (it is rare, but not unheard of to ask bitwise operation problems). Reduce the problems you look at in EPI to just the essentials based on your timeline. They have a “schedule” of recommended problems depending on when your timeline is so adjust accordingly.
@50 hours: you should be able to do easy/medium problems in under 20 min—10 min to get the pseudo code/idea and 10 min to write.
@80 hours: you should start to peak. No problem should be difficult, except maybe some DP problems. There are very few problems that you cannot solve optimally, but you should be able to solve every problem naively.
@100 hours: you should start feeling stressed and overloaded. This is the time to ramp down. Ramping down means to reduce your study time to about two problems a day (less than 40 minutes). If you are feeling still unprepared at this point, take a three-day break of not studying and skim back over everything, then continue where you left off.
Personally, I hit the stressed and overloaded point at 100 hours. I just briefly skimmed and re-read one chapter of EPI and browsed the chapter questions every night. I rarely put pen to paper to try and solve questions after this point.
You are ready to start interviewing when you’ve hit these checkmarks:
Everyone has a varying amount of time it takes to get there. So this WILL take a while.
If you are fortunate enough to have multiple offers for interviews, then take as many as you can. This is essentially free practice. As mentioned before, people who are bad at an activity are bad at self-critiquing at that particular activity. If you are practicing with someone who is as bad as you, you’re going to learn the wrong things or receive the wrong feedback. Interviews with people who are better than you/industry veterans is how you get practice with people who are better and have a taste of what it is like before the real deal.
For most engineers, their goal is a FAANG job—everything else is just a stepping stone for that. So why not use non-FAANG interviews as a chance to practice? At worst, they say no and forget about you. At best, they extend a job offer to you which you can use to negotiate a higher salary at another job and have a safety in case you don’t make it to FAANG. But you will at least have a testing/practice partner.
If you have a bunch of phone screens, I would stagger them out every five days or so. The fewer phone interviews you have, the more you should spread them out so you can receive feedback on how you did and apply them to the next phone screen.
Suppose you are inundated with a bunch of phone screens and on-sites. Schedule no more than two on-sites per week and do not make them back-to-back days. If you only have a handful of on-sites, stagger them apart by at least two weeks so you can get feedback from one interview to apply to the next.
Try not to do more than two phone screens a week. Your voice and mental energy will deplete very quickly otherwise. You will be talking a lot (one hour straight for phone screens and five hours straight for on-sites) so it is important to rest your voice.
However, if you want to be as crazy as me, I scheduled phone interviews every other day for about three weeks straight. Granted, I passed all of them but one.
For my on-site, I tried to only do two a week, but due to some time and offer constraints, I did my interviews with Google and Facebook back-to-back. On top of this, I had to fill in my eight hours of work right after and review merge requests and help my coworkers with bugs. My voice and social battery were completely shot for the next week because of that and I do not recommend this.
A cookie cutter schedule might look like this image below. Please do not just copy this. Allow yourself more time if you need to between on-sites and phone screens!
Out of all the companies, Google is the one that takes the longest to get back to you (over a month). So try to book it early on in your interview process unless you’re playing for “Google or bust” in which case, save the best for last.
If you are highly confident in your abilities and are likely to receive multiple offers, then you should consider the impact receiving an offer has on you. For most people, any job at FAANG will be sufficient and after receiving an offer, you will have zero motivation to continue practicing.
From what I can tell, after three offers, the motivation just drops to zero. This is what happened to me. I simply lost motivation to continue to practice and decided to cancel the rest of my interviews after 3 companies told me I passed.
On the flip side, if you are not certain of passing your interviews, you should try and stagger them out so that you have enough time to receive the feedback and apply it to your next interview on-site.
Therefore, if you are confident you will pass your interviews, you should schedule them weekly. Otherwise, schedule them for every other week or every three weeks.
So you’ve passed your phone screen. ‘Grats. Now on to the on-site.
Every HR recruiter sends you the same packet and tips and, to be honest, it's not that helpful. It's pretty much all the same between companies and you might find some useful resources. Some of the links should be skimmed so you know the history of the company. Nobody wants to talk to a candidate who has no idea what they’re applying for.
You may have 4-5 interviews back-to-back with bathroom breaks and a lunch break in-between. Pretty standard.
You should not be doing anything super special. Keep it simple. I don’t care if they’re offering you caviar. Stick with what you are comfortable with and used to.
The more you stray outside your comfort zone, the less comfortable you are. If you have studied while drinking water, then always go for the bottle of flat water. The more you can recreate the conditions of your study, the more you will perform the same as during your studies.
These are my personal tips.
I personally take 5mg. Since I know how it affects my body, I know how to time its usage. I will go from sleeping 7 hours to about 9.5 hours so I’ll plan to go to bed much earlier the night before. It takes about 1 hour for it to kick in.
Let’s say you need to wake up at 8am. Working backwards, you will want to be asleep by 10:30pm which means that you will need to take it at around 9:30pm.
Getting adequate sleep will mean you don’t need coffee. Also, definitely do a few test runs with it so you know how your body reacts.
I drink a fuck-ton of Red Bull and take two scoops of pre-workout just about every other day, but I won’t drink coffee before an interview. Interviews are high-stress and coffee adds to that by adding more physical energy into my body. It does nothing for me but make me more anxious in an an already anxious environment.
I am NOT advocating you take performance enhancing drugs, but the closest thing I’ve found is ADHD/ADD medication. If you need a psycho stimulant, take Adderall or Ritalin since it keeps you focused and mellowed out. 2.5mg is sufficient for most people (not the 5mg pills that are usually prescribed). You SHOULDN’T do this if you have not practiced or studied while using it or if you do not have a prescription.
Your job right now is to be confident in your own abilities, the practice you have done, and believe that your abilities make you the best candidate for the job. So act like it. If you second guess your abilities, you will second guess your decision-making and that can throw off your interview game.
I highly suggest you listen to 6ix9ine or some kind of hype, loud rap. Coming off as a little cocky is ok as a self-hype mechanism long as you are humble and willing to listen to the interviewer.
Hardcore hip-hop and rap helps you get into the mood. I listen to Stoopid and Gummo and sing scream listen to it in the car ride on the way to the interview. Also add in Rick Flair Drip by 21 Savage, Ether by Nas, and I Don’t Like by Chief Keef.
If you did well? Good, take the break. If you did shit? Reset and take the break. Don’t keep riding the momentum of the previous question. Ride the high and you will get too cocky and rush and make stupid mistakes. Dote on your failure and you will be hesitant. Reset your emotions and go to the bathroom for 2-3 minutes to help you reset back to square one.
As mentioned before, you are going to also be judged based on what questions you ask so make sure your questions are pertinent to the company you are talking to. I do not mean a shallow question about company culture. I mean more technical questions or questions about day-to-day operations.
For instance, Facebook was born from a culture of “move fast and break things”. Now that Facebook is a more mature product, does it still believe in that? How does it balance that mantra with the need to keep its heavily-relied on product stable?
These are the kind of questions you should be asking. In-depth. Questions that spawn more questions.
There are a few reasons I’m not going down the mass-market route.
First, it wouldn’t sell well. People want things to be spoon-fed to them, but they don’t want to put in any effort into the actual process. They want something in exchange without creating something of value, whether that thing of value is a product or improving themselves. They really just want the feeling of doing so and that's what they are paying for. I am not writing this guide for those people.
Most people would rather complain about being offended by this guide than actually trying any of the step-by-step procedures I’ve written.
Second, the nature of monetization makes monetizing this guide self-defeating.
Analyzing and walking through all the techniques and selling them defeats the purpose of exploration and discovery (which is critical to actually learning and understanding these algorithms). The practice of breaking down techniques and problems in a way that makes sense is something that only comes from doing it yourself, not watching someone else do it.
Gitting gud is about trying, making mistakes, and learning from them. It can mean wasting tons of time and hours trying to figure something out, but most people do not have the will to sacrifice that much to be exceptionally good at something.
My view on this has changed over the past two years. Since I’m able to provide an excellent service that I know is valuable, I have no problem staking my entire compensation in someone’s offers. This is not unlike what other people do—except they have much lower standards.
I would not be happy unless my client got a six-figure offer well above their current salary and was able to achieve extraordinary results. If I’m going to sacrifice six weeks of my life for someone, the results better be worth it. My goal is to maintain my absurdly high success rate and accept no compromises; I hope that my clients feel the same way. Plus, it's just straight up more rewarding to help people you believe in rather than trying to make a buck off of someone you don’t believe in.
My god, being forced to do someone else’s bidding for a buck seems like a terrible way to live. I guess that’s mass-market sales in a nutshell. What’s the point of money if you can’t buy your own freedom?
To make it fair and transparent, I make every client’s session free to watch.
“The incompetent cannot know they are incompetent. The skills you need to produce a right answer are exactly the skills you need to recognize what a right answer is.” – David Dunning
I’m not against it, but I don’t recommend it unless your interview partner is a better engineer than you. Even then, it is not a service worth paying for unless you’re looking for some kind of mentor/connection in the industry. Most engineers are focused on their own lives and stuff and don’t really have time for you anyway.
Chances are your interview buddy is probably someone who is a similar skill level as you. Therefore, his knowledge of when and how to help someone on an interview question is nil. He will probably be spoon feeding you answers unbeknownst to you. On the flip side, if he doesn’t help you at all, you’re better off just doing the questions on your own and using an anime figurine as an interviewer for practice.
However, if the interview partner is an industry veteran, he is probably a very valuable resource so do take him up on the offer.
I personally have never done mock interviews. I just write out my problems on pen and paper and talk about my thinking to myself anyway.
Referrals just move your resume to the top of the pile, but it doesn’t mean much if you don’t have the right qualifications or a strong enough resume. That said, you should always try to leverage one job or internship to get another job or internship and trade up, using a good performance on the coding interview as a way to get yourself into the company.
On my path, I started off doing unpaid internships at startups. I did an internship in Hong Kong the following year that paid me about $5/hour. Afterwards, I worked at a start-up while going to school. My salary barely covered my living expenses. Finally, I went to Apple.
So do not overestimate how long it takes to get that first job or how much your offer will be. A lot of people will tell you about amazing offers they get but I find it takes a few cycles to get there. More often than not, those high offers with little prior experience are a rarity.
A good rule of thumb is that for any given topic, technique, or data structure, if your code is > 20 lines, you are doing something wrong. Either your approach is not efficient enough (in terms of code length/meaningfulness) or the topic is not relevant. Try to strip away the null checks as much as humanly possible. A lot of functions nowadays provide ways to skip this so do try to find all of them.
I think most of them are nice people and you should be good to them. They are your only friend in this process. But at the end of the day, they are still headhunters working for the company that will hire you. They will be nice to you and try to get you the best offer they can and you should treat them well in return. You should skim the materials they send you but don’t take it too seriously. Your job is to ace the interview and represent your own best interests.
But do send them a nice thank you after every interview. All the recruiters have been super sweet and friendly to me and I almost wish I could send them a gift basket or something.
They just need to work hard and be able to afford the fee. I will not take someone’s money unless they can afford my fees and I can provide them an equivalent amount of value. All the sessions I do are free to watch anyway.
I’m just a stubborn egomaniac who hates losing. And when I say I hate losing, I really really hate losing. If I’m not good enough, then I get pissed off at myself for not being good enough and work until I am way better than good enough.
I’m fairly childish and most of my emotions teeter between being bored and pissed. In my mind, Google giving me an L3 offer is me failing the interview, even though I technically passed.
I’m also not that smart. I was a B+ student my entire life. I spend most of my free time watching anime, playing video games, drinking, and pretending I’m Snoop Dogg (if you know what I mean). If I was really smart, I’d have figured out how to consistently get L5 offers.
But if I want something, I do my homework and go full tryhard mode. Crank up the anime motivational music and let the games begin.
I just come from the school of “git gud”; either you get it or you get kicked out.
Probably. But if you want a hugbox that will validate your every emotion, go somewhere else. Seriously, just stop emailing me about what a dick I am. I honestly don’t care what you think about me. Whether you like me or not, you’re probably just wrong about me.
But hey, if you want to talk shit, at least have a better record than me before you open your mouth. What’s your success rate at ANY interview again?