"I will write a class".
I can't tell you how many times I have heard this sentence from candidates during coding interviews.
What's wrong with this sentence? Nothing, out of context, but let me add this little detail: this is usually the first sentence I hear when the candidate tries to tackle a problem.
I know that coding interviews can be very stressful. I also think that leading such interviews requires a lot of effort to avoid transforming them into nitpicking sessions. The candidate feels every single keystroke is scrutinized and analyzed. As if the destiny of the whole company depends on how fast you can code a function that reverses a string!
But even considering interview anxiety, I think such an approach reveals something wrong deeper in the way we approach problems as programmers. This is the result of a culture that mistakes tools for solutions, and if I can detect it in senior programmers, it means it already propagated into our teams and our companies.
When you face a problem (any problem), you need to devise a strategy to solve it. You need to know what to do. Otherwise, you are reacting and not acting.
When you practice any type of combat sport, you train your body to react to specific inputs (attacks) with automatic reactions (defenses, counterattacks). Still, you usually do it because in a real fight, you don't have the time to make a conscious decision.
Such "perfect" reactions result from constant and focused effort to transform consciously selected actions into involuntary ones. Without training, a pure reaction is usually an average response at best.
In problem-solving, we face the same challenge. Either we devise a strategy, or our approach will be clumsy and ultimately not efficient.
Imagine you were tasked to build a bridge between two sides of a river. Would your first concern be the specific type of hammers that the workers should use? After all, you can have ball-peen hammers, sledgehammers, brick hammers, and many other types. Choosing the wrong one might severely affect the performance of your workers.
That's hardly the first thing you should ask yourself. I'm pretty sure you agree that knowing the distance that the bridge should cover is much more urgent. Also, the type and amount of traffic it has to carry (walkers, cars, trucks, trains) is an important factor, and you should be concerned about the budget you are allowed.
Why are these questions more important than the ones about hammers? Because the answers to these questions can heavily influence the whole project. They are pillars of your architecture and not details[1]. My colleague Ken Pemberton always reminds me that most of the time we don't ask ourselves an even more important question: "What problem are you trying to solve?". In the example above, a bridge might not be the best solution in the first place.
At least when it comes to software projects, I think the process can be divided into three connected phases: decomposition, communication, implementation.
Macroscopically, a processing system is made of an initial state, some transformations or intermediate states, and a final state.
Usually, it's simple to identify the initial and final state, while it's harder to describe what happens between the two. So, we need to proceed iteratively, describing the system using black boxes and then opening each one of them, zooming in to describe what happens inside.
At any zoom level, from the 10,000 feet overview down to the description of a single function, you need to identify 4 things: the input, the output, the actors, and the data flow.
The input is what enters the black box. It has usually been decided while discussing a component that provides it as output. So, it is given, and if it turns out to be inadequate, we should take a step back in the design and question how we can provide proper input. The same is valid for the output.
The actors must be black boxes that accept data and transform it, and the data flow is how information is exchanged between the actors. This is clearly where it can take a long time to find a good solution, and we might need to go back and forth several times.
Let's look at an example. A search engine is a complicated piece of software, and implementing it is not a matter of 1 hour of work. But we can decompose it pretty easily, starting from the fact that the input of the system is a query and that the output is an ordered set of results. So, my overview of this component is the following: the user inputs a query, the query is processed, and the system returns a list of results, ordered by quality.
I didn't describe what "quality" is nor discussed the specific implementation of the system that stores all possible results. Those details are buried down somewhere at a certain level of zoom and are utterly useless here.
Any level of zoom in the decomposition can be described, and the amount of specific technical knowledge needed to understand the explanation should be directly proportional to the zoom level. You might have heard the quote:
"You do not really understand something unless you can explain it to your grandmother."
I believe this might be very offensive to grandmothers, but paraphrasing it, I would say that "There should be a zoom level at which the project is understandable by anyone who doesn't have a specific knowledge of the field".
Indeed, the problem of technical communication is that tech-savvy gurus are usually not able to decompose what they are working on into black boxes that are sufficiently abstract to be understandable by any human being. Please note this can happen to anyone, not only to programmers. I had to listen enough times to people working in banking, insurances, or project management (to name a few different fields) to know that they can be unable to describe their job or specific aspects of it without using 4 obscure words every 5 words, the fifth one probably being conjunction.
Being a blogger and an author, I want to add a consideration about communication. Explaining things is the best way to see if everything is clear in your mind, which is another way to read the previous quote (without involving grandmothers). The very same post that you are reading started as intuition, a small list of ideas, and so far has been rewritten 6 times. In the process, I understood the topics I am discussing much better than I did when I first felt the need to write them down.
Professor Sidney Morris, in the interesting video (above) about how to write proofs in mathematics, describes the process here:
While we don't need to aim to the same level of formality required to mathematicians who prove theorems, we can surely keep the spirit of the process: write down and define what you have, write down and define what you want to achieve. Then, think.
We tend to take for granted that we can think. After all, we do it all day long. But focusing our attention on a specific topic, giving it time, exploring it, considering questions about it, evaluating possible answers, all these things are increasingly unpopular. This is not the place for a critique of our society full of noise, where ideas, products, and works of art are watched for mere seconds before getting alike and passing into oblivion. But it is worth noting that thinking is not easy.
Implementing a black box might require a lot of thinking, and we have to accept this. It might require many rewrites, prove unsuccessful only after a certain amount of time, or even require a separate project to be properly managed. There are no shortcuts here.
What do we do during a coding interview? What are we trying to understand with this excruciating exercise that puts people in a pillory for one hour?
What we should do, in my opinion, is to help the candidate to show how they solve problems. We should facilitate a discussion along the lines of the three points: decomposition, communication, and implementation. As you can see, implementation is not avoided, it's a coding interview because there should be a part of it in which we write code, but it should be done only after we established a decomposition of the system.
I also believe that the assignment should be purposefully too complex to implement in a single one-hour session, and this should be explicitly communicated. This forces the candidate to design instead of rushing headlong into implementing the first requirement of the exercise without reading the rest. At any point, if the candidate is unable to implement a specific step, we can also move on to other steps and fake the input. This way we get many benefits:
As an interviewer, I value the decomposition phase much more than the part in which you show me how well you remember all the functions of the Python standard library in a stressful situation. The truth is that I look them up very often, and I don't look down on people because they don't remember the name of a method.
I have one hour to decide if you are a good addition to the company, if you can be a good teammate for my next project, and if (possibly with some training) you can be given the responsibility for part of the system. In that hour, I need to capture the main traits of your approach.
Don't get me wrong, I am a terrible nitpicker and probably on the brink of being OCD about some things, such as naming or tidying the code. But I try to take my own advice. What is the most important thing about you that I can understand? I think it would be extremely disappointing to discover that I hired someone who knows the standard library by heart but can't pick the right technology to complete a project before the deadline.
I understand that when you are interviewed, you feel like you are in a position of weakness and that you are sitting there at the mercy of an evil interviewer whose purpose is only to uncover what you don't know. I'm sorry if you had to face such interviewers. I had to, and I understand the frustration. My advice is: always remember that working for a company is giving your time and energy in exchange for personal growth. You might be interviewing for your dream job, but if the interviewer is not interested in you and your growth, it's probably not that useful for you to work with them.
So, as a candidate, you have a responsibility to show the interviewer how to solve problems. If you show how good you are at coding, you will impress only interviewers that are interested in your coding skills, and this is, in my opinion, a very limited part of what you can do as a programmer. You need to show that you can design, which is independent of the level you are at.
You need to show that you understand problems, that you can compare solutions, that you can take your risks picking one specific strategy and that if needed, you can stop at a certain point and say, "This is the wrong approach".
Design patterns are defined by Erich Gamma and his co-authors in their seminal book[2] with these words:
"[...] patterns solve specific design problems and make object-oriented designs more flexible, elegant, and ultimately reusable. [...] A designer who is familiar with such patterns can apply them immediately to design problems without having to rediscover them."
I want to focus on the words "solve specific design problems" because I notice that many people apply patterns without understanding the problem they are trying to solve. Even worse, they look at the world through the lens of the patterns they know, twisting the nature of problems to fit the solution they know.
Back to the original sentence. "I will write a class" is considered the go-to solution in OOP languages. We believe that, in an OOP language, whatever the problem, the solution is to write a class. So, our first move on the chessboard of the interview is to write a class. This is a dangerous misuse of a pattern such as data encapsulation, and an expert interviewer will checkmate us in one move. I saw candidates facing problems that could be solved in 10 minutes with two functions and a dictionary spending more than 50 minutes swamped in many classes, trying to figure out which object contained the data they needed at a certain point of the process.
Clearly, classes might be the best solution for some problems, but this should come at the end of your analysis. You write a class because you have data and functions that can be put together, which is valid for any other technology. Always ask yourself: what is the reason why I use this? What is the problem that I'm trying to solve?
We all make the same mistake here: we push (or at least accept) a culture in which we teach and learn tools as go-to solutions without teaching to identify and facing problems.
Programming languages, architectural patterns, algorithms. Those are all tools to implement solutions; they are not the solutions. You should learn them, down to the most minute details if you can, but never put them on the table before you understood the problem.
Alexis Carrel said:
"A few observations and much reasoning lead to error; many observations and a little reasoning to truth."[3]
If you want a clear example of the opposite, observe a programmer looking for help on an error the web framework or the compiler threw at them. Copy and paste the error message into Google, pick the first result (Stack Overflow), scroll down until you find some code, apply. I dare you to call this "engineering". Many times we don't even read the Stack Overflow question. We directly read the answer, not to mention the fact that many times we don't even read the error message!
I recommend reading an interesting article by Joseph Gefroh, Why Your Technical Interview Is Broken, and How to Fix It, where he discusses the various types of skills you can explore during an interview and which ones you should be interested in. In particular, I couldn't agree more with his point about algorithmic interviews, as I believe they are deeply flawed.
I also recommend having a look at the Guardian Coding Exercises and to read the description of the repository. I think they are a good example of tests that allow the candidate and the interviewer to work together and to discuss a solution. There is no "right" way to solve them, and many of them cannot be solved in 45 minutes, which is usually the time given to a candidate after an initial introductory chat.
I hope these short considerations helped you to see my point. We should all shift our gaze from the tools we have to the nature of problems and their solutions. We are missing an important step here, which is ultimately what defines a good engineer and is the most important thing you can learn in your career.
Observe problems, stop and think, devise a strategy, zoom out and zoom in. Learn to use tools, don't be used by them.
We need to push for this approach in our interviews, but also try to promote this culture in our teams and companies.
[1] see "What is a software architecture?" in Clean Architectures in Python https://www.pycabook.com
[2] Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Vlissides, Johnson, and Helm
[3] Réflexions sur la vie, Paris, 1952
Previously published here.