Preparing for your interviews — Leetcode is not enough

Introduction

The most common component of the software engineering interview is the algorithmic-style interviews. For algorithm interviews, you are given a problem whose solution is expected to be written in code within 45 min – 1 hour. The goal is not just to write a correct solution, but to solve it with better time/space complexity than a naive solution. You are generally graded on the performance and correctness of your solution.

If you’re preparing for your interviews, the best single resource is probably Leetcode. Leetcode is a platform for practicing algorithm questions marked based on difficulty and tagged by which companies have used them in real interviews. Like other platforms, Leetcode provides the questions and will check the correctness of your solutions according to test cases and run-time limits. Unlike other platforms (e.g. HackerRank), Leetcode’s customers are people practicing for their software engineering interviews.

In my experience interviewing as well as interviewing others, Leetcode is a good first approximation for practice because it implicitly tests for the end-goal of writing a solution that passes a battery of test cases with strict runtime requirements. However there are many components of the software engineer interview which can greatly affect your performance for which no amount of Leetcode practice will help you grow these skills.

What Leetcode is good at

As I mentioned Leetcode or a similar platform is perhaps the single best tool you can use. It will implicitly check for a combination of skills that are necessary to reach the end goal of writing correct code with strict runtime requirements.

Coding

You can’t solve an algorithmic question without being able to code. In this sense, Leetcode will help you answer an important question: are you capable of coding at the most basic level of proficiency?

Algorithmic Analysis

You also can’t solve an algorithmic question without having a solution of the appropriate time complexity. The test cases will often include extremely large inputs, so if you have an O(n^2) solution, it will have very clear timed runtime differences from an O(n log n) solution. In this way, you can clearly see if your solution is timing out of test cases, which would suggest you are not solving the problem with the appropriate time complexity. If you are capable of passing all test cases, this is a sign that you have devised a solution with the appropriate time complexity.

Problem Solving

Naturally algorithm questions aren’t about memorization. They involve higher-level problem solving skills where you combine your knowledge of data structures and algorithmic analysis to come up with a general algorithm which solves the question in the appropriate time complexity.

Data Structures

Without use of the correct data structures you may not be able to solve problems at all. For example, there is no good replacement for priority queues / heaps. Since different data structures have different time complexities for their operations, knowledge of data structures and algorithmic analysis are complimentary skills, both necessary for solving problems.

Edge Cases for Correct Inputs

Code correctness is paramount for any solution. You want your function to work properly for any appropriate input that it may receive. If it well-formed input causes incorrect behavior, then your code is incorrect. Leetcode is pretty good at including test cases that include coverage of edge cases. If you can write code that passes Leetcode’s test cases on the first attempt, you can probably handle edge cases in your interviews.

What Leetcode is bad at

In my experience interviewing as well as interviewing others, there are many aspects of the interview that are not covered by solving Leetcode-style problems, especially at selective companies. This is unfortunate because there is no other resource I am aware of that will help you practice these skills. These things are usually not explicit grading criterion but nonetheless can hinder your progress towards writing correct code within the time constraints.

The thing that ties together all of the things that Leetcode is good at is that it’s easy and scalable. It’s not a coincidence that Leetcode questions involve no ambiguity about correct inputs/ouputs, grading is easily quantifiable, and that everything is extremely structured. Leetcode is not testing the full range of what’s graded in an interview but rather they are testing a balance of what is important and what is easily measured. Leetcode is a business after all, and they implementing features that bring the highest ROI. I say this to underscore that there are clear reasons that Leetcode (or any other platform) exists in exactly the way it does. As you might expect, the things Leetcode doesn’t help you practice with are harder to grade through software in a way that a human grades it:

Requirements Gathering

In a real interview setting the question is often deliberately underspecified. What is expected of you is to properly scope out the requirements of the problem — the inputs, outputs, what to return in special cases, and coming up with good examples to understand the question. In a professional setting these skills are extremely important because mistakes of this nature are so costly. Because this normally happens in a dialog between interviewee and interviewer, it’s not easily automated in code.

Code Quality

Code quality is hard to quantify in an algorithm but easy for humans to understand. Most code in production environments is written once and read many times. Code can often last years, or even decades until it is removed or deprecated. Writing easily understandable code is a valuable skill because there is a shared understanding that clean code is easier to maintain. There are normally many ways to write code and you want to do it in a way that minimizes complexity, state, and lines of code given the constraints of your programming language.

Testing Bad Inputs

Since writing correct code consistently in production environments is an ideal and not a reality, handling error cases is important. Occasionally you may be asked during interviews to deal with these error cases (usually bad inputs), such as error cases and value errors. You may also be asked to handle these errors with exceptions if your language provides exception handling.

Familiarity with the Language

You are expected to understand the language you are interviewing in very well. You need to know how to import the correct libraries, what libraries are available to you, what methods/properties are available for built in classes, string processing, sorting, etc. This extended list is pretty important and I may elaborate on it in another blog post. But the short version is that you need to know your language well enough to never need to look anything up, because you won’t have that opportunity in an interview anyway. And if you don’t know your language well and fail to use built-in functions or libraries, your interviewer won’t be able to disambiguate that from writing unclean code.

Manual Testing

Manual testing is how you test your code when you don’t have access to a compiler. This is an important skill because the first line of defense against introducing bugs is understanding how you code works by looking at it. Due to the high cost of bugs in production environments (in terms of downtime, degrading product quality to customers, engineering time investigating issues, etc.) your ability to find bugs on your own is highly valued. The goal of manual testing is to execute the code like a manual debugger would, going through the code line by line and keeping track of state. Many mistake manual testing to be testing the algorithm design when it is actually about testing the algorithm implementation. This is a very common part of the interview for people to make mistakes, and these mistakes happen when they execute their mental model of what the code is supposed to do rather than executing what the code actually says.

Whiteboarding

In an in-person interview you will likely be coding on a whiteboard and not a computer. There is no good reason for this except network effects — whiteboards were used for interviews when computers were still expensive (or company budgets were low), and now we’re all stuck with it like the QWERTY keyboard. This is regrettable for a number of reasons I outline elsewhere, but what is important to keep in mind is that you are practicing for your interviews on a computer but using a totally different medium for transcribing information in real settings. With whiteboards your transcription speed is slower, you can’t shift lines down, shift characters right, or copy-paste. You will find that whiteboards are far less efficient than keyboards and because of this you will need to practice whiteboarding in a way that minimizes the time spent making edits.

Writing Code from Scratch

When you have access to a compiler, like for many phone interviews, you will often be writing code from scratch. For languages like java where your functions may need to be static public with a main function, this can be an important thing to practice. Make sure you can always write your solutions form scratch. Leetcode provides a function signature because they need to grade you against an objective API, but in a real interview setting that function signature hooked into a main function will not be provided.

Conclusions

Leetcode is a great tool for interviewing but it has fundamental limitations since everything is graded by code, not humans. This means Leetcode may help you build skills that are easy to test in code but will have poor coverage of skills that are hard to evaluate with code. Leetcode tends to be good at improving problem solving, algorithmic analysis, data structures, handing edge cases, and basic coding proficiency. Leetcode tends to be poor at requirements gathering, code quality, error handling, language familiarity, manual testing, whiteboarding, and coding from scratch. If you are preparing for your interviews it would be good to have these weaknesses in mind because it means you will need to consciously work on them as they are not implicitly tested by solving Leetcode-style problems.