Coding, Fast and Slow
Some of the coding problems, like ones from the interviews, took me months to solve. I was wondering why does that happen? Why wasn’t I able to do it so much faster? It happened to me to find a hint — on the pages of the book “Thinking, Fast and Slow” by Daniel Kahneman.
That cursed interview day
As you probably already know, I am a programmer, and one day I was a CS student. In my last year of studying I was craving to start applying my knowledge in working environment. After I discovered that Google had open internship positions in my hometown, St-Petersburg, I didn’t hesitate and sent my CV. To my surprize, a phone interview followed. Pretty awkward one, I must say, since it wasn’t yet common practice to do interviews via a shared Google Doc, so it was more like imaginary coding.
Then they invited me for a double on-site interview. This time we used pen and paper, and the problems were much, much harder.
As it often happens, the problems had a straightforward but extremely inefficient solution, and a correct and efficient one. There was one particular problem that I struck me pretty hard. I do not remember the exact details, only that it was about counting a substring length in a string of characters.
On the interview day I was only able to come up with a recursive, exponential solution; I knew perfectly well that there should be something polynomial at least, but I couldn’t quite find it.
It was not the only problem on which I didn’t perform well enough. I didn’t get the internship. That problem got stuck somewhere in the back of my brain for a while.
Arena for us fellow coders
After such a defeat I started researching on the matter: how people prepare for coding interviews at Google? How do they get better? How to get that job at Google? Pretty soon I found that it is popular to participate in TopCoder competitions, where you have to solve problems that look very much like the ones from the interviews, and also in limited time. And also, they had a thing called “Arena”: a platform where the competitions are run and, most importantly, where you can practice.
The competition would consist of three problems: one trivial, one medium, and one hard. Everybody would start coding at the same time, have same set of problems, and 70 minutes to submit the results. Each problem would come with a few test cases, which could be automatically executed by the platform.
I started practicing, and quickly realized that I can solve most of the trivial problems relatively quickly. The solution had a performance cap: the program should finish in 2 seconds. For the easy problems it was more than enough even for not the most optimal solution (actually, the solutions were usually dirty). The medium-sized problems I sometimes managed to solve, but taking a lot more time (and some luck).
I participated in a couple of matches, solved the easy problems, and got my just-around-average points. Few months after the cursed interview I was still on time to get some internship or a junior job before I graduate.
Test me
Working at Yandex was my long-lived dream. When I was in my first years of the Uni, their job openings were scarce and required high-level C++ development skills, and good amount of experience, which I didn’t have. But then internships started to appear, and a hope was born. There were many, actually, I applied to few of them, and I waited.
They called me and proposed to proceed with the internship in test automation. I said fine, we agreed on an interview date and I started preparing, same place, TopCoder Arena.
There was just one single on-site interview, and it was way easier than what I had at Google. The coding problem was to implement some linked list operation, it was so easy to me that I couldn’t believe how lucky I was. Moreover, it was one of the problems I studied with the N. Wirth book “Algorithms and Data Structures”.
The other problem was about generating test cases for a little program, and reasoning about it. You could see that all the tasks I faced were familiar to me, even testing was not that different from what TopCoder problems required.
They liked me, and I got hired — as test automation intern.
Test me more
Working at Yandex changed me a lot. It was very energetic and positive environment, focused on doing things that matter, on making pragmatic choices. Yet I quickly discovered that problems from TopCoder had very, very little to do with the actual coding and testing problems from the industrial environment. I realized that I will not get better at solving Google puzzles just by being a cool guy and great engineer at my current job. That’s why I continued practicing in my spare time.
I remember, it was something like one year and a half later when I finally found the solution to that cursed problem from the first Google interview. I was writing an email to my colleague, suddenly some vague idea crossed my mind, I stared at the monitor and told myself:
“The solution has to be linear. How to solve this characters-in-the-substring problem in linear time? We have to go through the string exactly once. How can we compute what we need in one go?”
And that was it. In half a minute I had a complete picture. It was beautiful. O(N) solution. How could I not come up with it that day? Or better, how can I make myself able to solve such problems fast?
I didn’t have a single idea. It was only few years later when I got a hint.
Test me harder
I was doing just fine with my job. One thing they do not tell you is that once you enter a company, they don’t need you to be all-that-smart as you were during the interview. They care only if you manage to do your job; and if this kind of job was done previously by somebody, that is, most of the jobs, it’s pretty hard to screw up if you just learn and apply the existing solutions.
One could say, I got “promoted”, and became a full-time engineer. Then I got myself a new project — with existing automated tests.
These were backend tests, which means, they were programs testing other programs that do not have any UI whatsoever. This was a good thing, since I liked programming and I didn’t like manual testing. There was also a bad thing.
Cloud computing wasn’t there quite yet: we were using bare metal machines, ssh-ing on them, copying stuff with scp and such. So the tests were written in the only language that permitted all these operations and was fast to write (to the guy who worked with the project before me).
They were written in bash.
If you are not crying and weeping inside already, please let me convey how complicated it was to maintain the tests written in bash.
First of all, by default bash does not propagate failures. If some of the steps failed, and you didn’t check explicitly, bash just goes on like there was nothing special. Imagine a person driving a truck who has just run over a pedestrian. What a normal person would do? Immediately stop, shocked, and try to help the poor guy. If it doesn’t happen, it means that the driver is either a maniac, a criminal, or in some other way unwell.
The same goes with programming languages. A decent [i.e. conventional] programming language will break immediately upon failure (Java, Python, C++, please continue). But not bash.
Second biggest inconvenience is that it is very hard to write abstract code. It is very impressive and concrete when it comes to manipulating files, processes, device status and such. But when it comes to classes and data structures, there are none. Every program on bash is basically a combination of invocations of programs written in C where data is passed either through command line arguments, or standard IO, or files on disk.
My quite natural decision was to refactor the bash scripts as much as I can, enable raising errors, add code reuse were possible, make code compliant with style guide recommended by developers from Google… It seemed that the end of work is near, that it is much better to spend few hours today to do this little enhancement to the existing set of bash scripts than to think about anything else.
After a year or so of constant wrestling with legacy bash code I realized, very clearly, that it was not worth it. All that effort I spent could have been used to rewrite the essential components in Python, which would ease the maintenance tenfold, and would have already started paying off.
I was resenting that decision to “purify” the existing bash code, to “make it right”, to have wasted so much time. I wondered how can I avoid these decisions that I later regret? Why wasn’t I able to see that all this battle is futile? I had the intuition that bash was not a good tool, why didn’t I follow it?
As I told you before, it was much later when I discovered a possible explanation. So, how did it happen?
Let there be Thinking, Fast and Slow
Since then I left my job at Yandex, used my TopCoder skills to pass an interview and get a job offer as a software engineer, and finally move to Trento, Italy. Although I tried to get that job at Google once again, I didn’t pass the first round of interviews and started considering myself simply not good enough as a programmer for the Google scale. I embraced the facts and stopped caring for a while, which opened my mind to new opportunities — like reading random blog posts on the Internet about psychology.
That’s when I found that blog post about “breathtaking” and “mind-blowing” book of Daniel Kahneman called “Thinking, Fast and Slow”.
My inner voice told me:
“Wow, that’s gotta be interesting! Maybe there are the answers!”
From the vague description in the blog post I could understand that the author is a Nobel Prize winner in economics, but he’s a psychologist, and he probably knows something about how we think.
“Right, how we think. Probably he knows how we solve hard problems! Because to solve problems we need to think slowly!”
Instead, the book was talking about rationality, irrationality, cognitive biases, and decision making in risk situations. If you never heard about it, cognitive bias is an effect that causes our minds to drive away from what seems rational. It is like a blind spot in our eye: we can’t quite see a particular part of the picture, but our brain makes us believe that this spot does not exist.
Remember that cursed interview problem? I was wondering why I could not figure it out that the time, and it seemed so easy and obvious when I did solve it. Turns out that I became a victim of hindsight bias, the “I knew it all along” effect. I just didn’t have enough experience solving these sport programming problems which Google likes to ask on the interviews. Later, after I did practice, I actually became able to solve, at least the easy ones, and pass those interviews in different companies.
The same thing struck me with the legacy tests written in bash. After a year of wrestling it became clear that the outcome was not worth the effort. Strong resentment that I felt was due to heavy hindsight, which was based on the knowledge available at the time. Our brain is not good at identifying in which state was its knowledge a year ago. Did I know that the bash scripts won’t look much better? I didn’t, only after refactoring them I obtained that knowledge.
This was not exactly the answer I was looking for. I wanted to become a person able to solve problems instantly, not to discover that my brain has fundamental flaws.
Hug your bias
The “Thinking, Fast and Slow” book covers a lot more than hindsight bias. It explains the reasons behind biases, and why they play huge role in situations with uncertainty. When we need to make a decision in a situation where we simply do not have enough information, and the decision should be made soon, we can’t rely on rational thinking. Our logic is not able to make sense of a puzzle that misses pieces.
Hence our brain has got to make sense of what it sees without logic, instantly. In fact, our brain is full of such shortcuts; their purpose is believed to be in helping us survive in life-threatening situations.
Some time passed, the ideas from the book soke in, and I developed an understanding that I can’t avoid my own biases, and I have to learn how to deal with them. One major shift for me was to allow myself to spend a lot of time preparing and analysing the subject before making an important decision. Now I know that if I overlook something that will appear obvious later I will resent it (due to hindsight effect), and I am better off making sure I gather all possible information to take the best decision I can.
Did I stop making decisions that turned out to be bad? No, not really. But now I know that I did my best and I don’t resent it. Do I always have the opportunity to take all the time I need? Neither. In fact, I sense how uncomfortable such decisions are for me; but yet again, being aware of such condition is already a big step up. In the end, it helps in building up self-confidence and acceptance of yourself the way you are.
Why Coding, Fast and Slow?
Dear reader, I really appreciate that you read this piece and I hope you found something new to you and had a good time. In order to thank you I would like to explain why I chose this title. You might be puzzling yourself, and I sure don’t want this to happen.
This little story is about one of many ways the book “Thinking, Fast and Slow” changed my mindset as a programmer. Just the hindsight concept was enough to explain why some of the code looks good when it is being written and looks ugly few months later, when you are more experienced. Coding is essentially thinking and decision making, and it is as flawed as our thinking is. The brain is the main tool of a programmer, and I believe knowing this tool a bit better will help a lot in becoming a better programmer and better self in general.
Thank you once again, and give your cognitive bias a hug!