May 1st, 2015
In Technology
1 Comment
If you enjoy this article, see the other most popular articles
If you enjoy this article, see the other most popular articles
If you enjoy this article, see the other most popular articles
Embarrassing code I wrote under stress at a job interview
(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com, or follow me on Twitter.
(Note: this post was, in part, inspired by John Lawrence Aspden’s post about FizzBuzz.)
I write terrible code when I go to a job interview. That’s mostly because, when they ask me to solve a coding question, I get nervous. I thought it might be entertaining if I wrote about one such encounter.
Recently I went to a job interview, at a company in New York that had once built their stack (for managing online advertising) in Ruby but who are now transitioning all of their stuff to Clojure (Adaptly).
On this particular day, I was to talk to 3 of their senior developers, and 2 of them would hit me with their favorite questions about how to find something in a given number series.
When I got to the company office, they put me in a conference room and sent in one of their senior engineers. He asked me to fire up a REPL, so I launched Emacs and then “nrepl-jack-in”. I was ready to go, but I was also very nervous, because of these 3 reasons:
1.) I’ve got a guy looking over my shoulder and he’s watching every mistake I’m making
2.) I’m suddenly self-conscious about the prettiness of my code — it doesn’t matter if I can solve the problem, since I’m also competing against candidates who can solve the problem, so the real competition is probably about how elegant and idiomatic our code is
3.) this isn’t just about getting the job done, it’s also about being quick — the pressure to move fast is strong
I was typing code into a live environment, which would then execute my code when I hit “enter”. For those of you who haven’t seen the REPL in Emacs, my screen looked like this:
In addition to the above problems, one extra problem I eventually faced wasn’t Clojure or math, it was English — we had a misunderstanding in our native language!
He asked me if I knew what the “Happy number sequence” was. I said I had never heard of it. He described it like this:
“Take each digit in a number and square it, then add the sums together. Keep doing this recursively.”
The next sentence of English is where the problem started. I thought he said:
“The sequence will either go to one or to infinity.”
but he actually said:
“The sequence will either go to one or infinitely.”
He then offered an example of how to find the next number in the sequence:
“Suppose you start with the number 31. Well, 3 squared is 9 and 1 squared is 1, and 9 and 1 added together is 10, so the next number in the sequence, after 31, is 10.”
And then, with “10”, the “1” squared is 1 and the “0” squared is 0, and when you add 1 and 0, you get “1”, therefore we know that “31” is part of the Happy sequence, because, after 2 iterations, it ended up being “1”.
That much was obvious, but I could not figure out how to test a sequence that goes to infinity — how would I know when to stop testing for more numbers? I mean, I might get to a septillion, but I’d only be taking a small step toward infinity — and then I would need to keep testing till I get to infinity… which is impossible? But I wanted to appear smart, so I didn’t express any confusion. I was thinking that if I dove into the problem, then it would eventually make sense to me. So I started coding in the REPL. The first thing I needed was a function that would find the next number in the sequence, and this would surely be easy:
user> (defn [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc) (recur (rest snc) (+ (+ (* (first snc) (first snc)) sum)))
but that got me:
RuntimeException EOF while reading, starting at line 4 clojure.lang.Util.runtimeException (Util.java:219)
Oh hell, why am I so clumsy? I tried again:
user> (defn [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (+ (* (first snc) (first snc)) sum))))))
and that got me:
IllegalArgumentException First argument to defn must be a symbol clojure.core/defn (core.clj:277)
Embarrassing! And now I felt even more nervous! So I tried:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (+ (* (first snc) (first snc)) sum))))))
and I got:
CompilerException java.lang.RuntimeException: Unable to resolve symbol: sum in this context, compiling:(NO_SOURCE_PATH:7:10)
Ugh! What a stupid typo! I tried again:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (+ (* (first snc) (first snc)) snc-sum)))))) #'user/happy
Finally! At least it compiled! Now maybe I can repair my damaged credibility! Let’s try to use it:
user> (happy 31) ClassCastException java.lang.Character cannot be cast to java.lang.Number clojure.lang.Numbers.multiply (Numbers.java:146)
Oh, good lord. This was the only way I could think to get each digit in a number:
snc (str x)
but this actually gave me a sequence of Characters, not strings. In the real world, I would have stopped at this point, gone to look at the documentation, and maybe found a more elegant way to do this. However, under pressure of time, I just started piling on bandaids to try to fix things enough to get it to work:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (+ (* (int (first snc)) (int (first snc))) snc-sum)))))) #'user/happy user> (happy 31) nil
Damn. I forgot to return any value from the loop. So I tried again:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (+ (* (int (first snc)) (int (first snc))) snc-sum)))) snc-sum))
And I get:
CompilerException java.lang.UnsupportedOperationException: Can only recur from tail position, compiling:(NO_SOURCE_PATH:5:4)
Now I am angry with myself. This whole process is dragging on too long, and I keep making simple mistakes. And it’s a job interview!
I try again:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (+ (* (int (first snc)) (int (first snc))) snc-sum))) snc-sum))) #'user/happy
Finally! It compiled! Let’s see it work:
user> (happy 31) 2601
Ah, damn! The correct answer is “10”, but I am getting 2601! What the hell went wrong?
At this point, the guy interviewing me is starting to get bored. I can see his eyes drifting elsewhere, he starts checking his phone for messages. I am thinking that in his mind he has already decided that I’m a pathetic loser. In a different industry, he might simply shout “Next” and kick me out and drag the next applicant in, but our industry is slightly more polite than that. So he waits for me to stop screwing up. But he looks very bored.
I notice that I am, for some strange reason, summing twice, so I take out the redundant “+”:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (* (int (first snc)) (int (first snc))) snc-sum)) snc-sum))) #'user/happy user> (happy 31) 2601
Damn! Damn! Damn! What the hell is wrong with my code?
In retrospect, the error is obvious, but under the pressure of time, my mind was racing and I was not seeing the problem, so I added some print statements so I could better understand what was happening:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (print (rest snc)) (print (first snc)) (if (seq (rest snc)) (recur (rest snc) (+ (* (int (first snc)) (int (first snc))) snc-sum)) snc-sum))) #'user/happy user> (happy 31) (1)3()1 2601
Hmm, so, the sequence of numbers is exactly what I thought it would be, so everything should work perfectly, but it is not because…
Oh, right! I am casting a string to an integer, and getting back the ASCII value of the string. Awful!
So, I try to fix that:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (* (.parseInteger (first snc)) (.parseInteger (first snc))) snc-sum)) snc-sum))) #'user/happy user> (happy 31) IllegalArgumentException No matching field found: parseInteger for class java.lang.Character clojure.lang.Reflector.getInstanceField (Reflector.java:271)
Hell! What is the right method call? I’ve done this many times before, I just can’t recall, right now, the way to write this. I turn to the guy and ask him permission to look this up in my old code. He gives me permission. I look at some code that I’ve written and see the correct way, and so I can do this correctly:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (* (Integer/parseInt (first snc)) (Integer/parseInt (first snc))) snc-sum)) snc-sum))) #'user/happy
It compiles! So I try it:
user> (happy 31) ClassCastException java.lang.Character cannot be cast to java.lang.String user/happy (NO_SOURCE_FILE:7)
FML!!!!!
I know there must be something elegant I can do here, but for now I go for the fastest shortcut I can think of:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (+ (* (Integer/parseInt (str (first snc))) (Integer/parseInt (str (first snc)))) snc-sum)) snc-sum))) #'user/happy
Holy hell, this code is ugly! If someone came into a job interview, and I was the one doing the interview, I would have some doubts about the candidate if I saw them write this code.
But anyway, I try it:
user> (happy 31) 9
Hmm, very close! I want “10” but I get “9”. What did I do wrong? Oh wait, I forgot to add in the “false” clause of the “if” statement! That is, what happens if we don’t recur?
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (do (print (first snc)) (+ (* (Integer/parseInt (str (first snc))) (Integer/parseInt (str (first snc)))) snc-sum)) snc-sum)))) CompilerException java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 2 args, got: 3, compiling:(NO_SOURCE_PATH:5:4)
Uh, so, I have a bracket in the wrong place? I have keybindings set up so that Control-1 takes me to the start of any form, and Control-2 takes me to the end of any form, so I can usually, rather easily, see how the brackets line up. But that doesn’t save me from bad thinking under stress.
Maybe I’ll add in a print statement again:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (first snc)) (recur (rest snc) (do (print (first snc)) (+ (* (Integer/parseInt (str (first snc))) (Integer/parseInt (str (first snc)))) snc-sum))) snc-sum))) #'user/happy user> (happy 31) 3 9
So, this never gets to the “1”, it only does the “3”. Why is that? Maybe instead of this:
(seq (rest snc))
I should do this:
(seq (first snc))
So I try:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (first snc)) (recur (rest snc) (do (print (first snc)) (+ (* (Integer/parseInt (str (first snc))) (Integer/parseInt (str (first snc)))) snc-sum))) snc-sum))) #'user/happy user> (happy 31) IllegalArgumentException Don't know how to create ISeq from: java.lang.Character clojure.lang.RT.seqFrom (RT.java:505)
Yeah, what a stupid idea. I change it back, since that is obviously not where the problem is:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (do (print (first snc)) (+ (* (Integer/parseInt (str (first snc))) (Integer/parseInt (str (first snc)))) snc-sum))) snc-sum))) #'user/happy
But where is the real problem? Oh, I see, in the “false” clause of the “if” statement, I need to sum the results:
user> user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (do (print (first snc)) (+ (* (Integer/parseInt (str (first snc))) (Integer/parseInt (str (first snc)))) snc-sum))) (+ (first snc) snc-sum)))) #'user/happy user> (happy 31) ClassCastException java.lang.Character cannot be cast to java.lang.Number clojure.lang.Numbers.add (Numbers.java:126) 3
FML!!!! FML!!!! FML!!!! FML!!!! FML!!!! FML!!!! FML!!!! FML!!!! FML!!!!
The guy who is interviewing me has mostly stopped watching, because he’s already decided there is no chance in hell that they will be hiring me. He is looking at his phone. I half expect him to start playing Flappy Bird.
I realize now that the verbose and horrible “Integer/parseInt (str” needs to be applied to the sum at the end of the “if” statement:
user> (defn happy [x] (loop [snc (str x) snc-sum 0] (if (seq (rest snc)) (recur (rest snc) (do (print (first snc)) (+ (* (Integer/parseInt (str (first snc))) (Integer/parseInt (str (first snc)))) snc-sum))) (+ (Integer/parseInt (str (first snc))) snc-sum)))) #'user/happy user> (happy 31) 3 10
Awesome! I finally got “10”! I finally got the right number!
And yet, I am only at the start of this problem. Writing a simple function to find the next number in the sequence was suppose to be the easy part, and I just wasted 10-15 minutes on it!
So, how do I find out if a number is in a sequence, when the algorithm might lead to infinity? I had previously postponed the question, but now I came back to it. Again, our problem was English, not Clojure.
Thinking out loud, I said: “If the sequence can go to infinity, then I could create a generator that returns the numbers lazily… but at some point we would need to realize those values, to check them… ”
“That is one way to think about it,” he said.
I kept thinking out loud: “…and if they go to infinity, then they will crash my machine…”
“Are you sure?” he said.
I thought long and hard about that. Was I missing something obvious? Was there a way that my machine could count to infinity?
I was quiet a long moment because I was worried that whatever I said next would make me look stupid.
“Well, I could pass in some bound, like perhaps 10,000. I could pursue a finite number of tries before giving up.”
“Are you sure?” he repeated. “Are you sure the numbers go to infinity?”
“Didn’t you tell me that they go to infinity?”
“No,” he said, “I said they go infinitely.”
Finally, the light dawned in my head.
“Oh,” I said, “So they loop?”
“Yes.”
“Oh!” I finally got it. “So they loop over a finite set of numbers?”
“Yes.”
“Ah! So I only need to store the numbers in a set and then I can see if I start to loop?”
“Yes.”
Okay, so now I got it. I could store the numbers in a set, and if I ever saw the same number twice, then I knew that the number that I started with was not in the Happy sequence.
user> (defn happy [x] (loop [snc (str x) snc-sum 0 seen-so-far #{}] (if (seen-so-far (Integer/parseInt (str (first snc)))) nil (if (seq (rest snc)) (recur (rest snc) (+ (* (Integer/parseInt (str (first snc))) (Integer/parseInt (str (first snc)))) snc-sum)) (conj seen-so-far (Integer/parseInt (str (first snc)))) (+ (Integer/parseInt (str (first snc))) snc-sum))))) CompilerException java.lang.RuntimeException: Too many arguments to if, compiling:(NO_SOURCE_PATH:5:7)
Ah, stupid! I’ve got a bracket in the wrong place for that second “if” statement. I’ll try again:
user> (defn happy [x] (loop [snc (str x) snc-sum 0 seen-so-far #{}] (if (seen-so-far (Integer/parseInt (str (first snc)))) nil (if (seq (rest snc)) (recur (rest snc) (+ (* (Integer/parseInt (str (first snc))) (Integer/parseInt (str (first snc)))) snc-sum) (conj seen-so-far (Integer/parseInt (str (first snc))))) (+ (Integer/parseInt (str (first snc))) snc-sum))))) #'user/happy
Great! It compiles! So now I can:
user> (happy 31) 10
Wait, what am I doing? The “happy” function simply finds me the next number in the sequence. I need a function, higher up the chain, that can loop through the whole series, and call “happy” on each iteration of the loop. I reset the “happy” function to the previous version that was working, and then I start on a new function:
(defn find-happiness [starting-integer] (loop [test-integer starting-integer seen-so-far #{}] (let [next-integer (happy next-integer)] (if (seen-so-far next-integer) nil (recur next-integer (conj seen-so-far next-integer))))))
which gives me:
CompilerException java.lang.RuntimeException: Unable to resolve symbol: next-integer in this context, compiling:(NO_SOURCE_PATH:3:27)
Sloppy! I am not impressing myself.
I have to take a deep breath and think, for a moment, about what I want to return. By this point, so much time has dragged by that the guy who is suppose to be interviewing me has largely stopped paying attention to what I’m doing. I figure the interview is a bust anyway, so I can take a moment and think about things clearly. The pressure of time disappears once I realize there is no chance I am getting this job.
I know I want this:
(if (= next-integer 1) true
That is, if the next number in the series is ever 1, then we know the starting number does belong to the Happy sequence, so we should return true.
And I know I want:
(if (seen-so-far next-integer) nil
That is, if I have a set called “seen-so-far”, and I store all the numbers that I discover as I iterate through sequence, then if ever I see a number twice, I can conclude that the series is settling into an infinite loop, and I can return false, because then I will know that the starting number is definitely not part of the Happy sequence.
So this should work:
(defn find-happiness [starting-integer] (loop [test-integer starting-integer seen-so-far #{}] (let [next-integer (happy test-integer)] (if (= next-integer 1) true (if (seen-so-far next-integer) nil (recur next-integer (conj seen-so-far next-integer))))))) #'user/find-happiness
So I try it:
user> (find-happiness 31) true user> (find-happiness 32) nil user> (find-happiness 33) nil user> (find-happiness 34) nil
Nice!!!! Very cool!!!
How about the first 100 numbers?
(map find-happiness (range 100)) (nil true nil nil nil nil nil nil nil nil true nil nil nil nil nil nil nil nil true nil nil nil nil nil nil true nil nil nil nil true nil nil nil nil nil nil nil nil nil nil nil true nil nil nil nil nil nil nil true nil nil nil nil true nil nil nil nil nil nil nil nil nil nil true nil nil nil nil true nil nil nil nil true nil nil nil nil nil true nil nil nil nil true nil nil nil true nil nil nil nil true nil nil)
Hmm, cool, but that is hard to read. Let me add in some print statements, so I can better understand the output:
user> (defn find-happiness [starting-integer] (println (str starting-integer)) (loop [test-integer starting-integer seen-so-far #{}] (let [next-integer (happy test-integer)] (if (= next-integer 1) (do (println " true ") true) (if (seen-so-far next-integer) (do (println " false ") nil) (recur next-integer (conj seen-so-far next-integer))))))) #'user/find-happiness
And then:
user> (map find-happiness (range 100)) (0 false 1 true 2 false 3 false 4 false 5 false 6 false 7 false 8 false 9 false 10 true 11 false 12 false 13 false 14 false 15 false 16 false 17 false 18 false 19 true 20 false 21 false 22 false 23 false 24 false 25 false 26 true 27 false 28 false 29 false 30 false 31 true 32 false 33 false 34 false 35 false 36 false 37 false 38 false 39 false 40 false 41 false 42 false 43 true 44 false 45 false 46 false 47 false 48 false 49 false 50 false 51 true 52 false 53 false 54 false 55 false 56 true 57 false 58 false 59 false 60 false 61 false 62 false 63 false 64 false 65 false 66 false 67 true 68 false 69 false 70 false 71 false 72 true 73 false 74 false 75 false 76 false 77 true 78 false 79 false 80 false 81 false 82 false 83 true 84 false 85 false 86 false 87 false 88 true 89 false 90 false 91 false 92 true 93 false 94 false 95 false 96 false 97 true 98 false 99 false )
Very cool!
And then I wanted to test one of these by hand just to make sure everything was working the way it should. It says that 97 is in the Happy sequence, so I tested to see if that was true:
user> (happiness 97) CompilerException java.lang.RuntimeException: Unable to resolve symbol: happiness in this context, compiling:(NO_SOURCE_PATH:1:1)
The level of self-sabotage is impressive, isn’t it?
Let’s try that again with a function I actually wrote:
user> (happy 97) 88 user> (happy 88) 72 user> (happy 72) 51 user> (happy 51) 26 user> (happy 26) 10 user> (happy 10) 1
Cool! It does work! And this is a cool number series!
Needless to say, the job interview was a disaster. I think they want someone who can write something like this in 5 minutes, but between the English-language confusion over the definition of the sequence, and the many mistakes I made, this dragged on for almost 30 minutes.
After I was done, the guy said something polite about “good effort” and then he said he would sent in the next engineer.
A few minutes later the next engineer came in. He asked me if I knew what the Collatz number sequence was. I said no. He drew a definition of it on the white board. He asked me to write some functions that would allow me to find the longest iterations through the Collatz sequence, given a set of starting numbers.
What followed was similar to the above: for more than 30 minutes I stumbled through the process, making dozens of mistakes as I went.
Interviews are weird: the pressure of time, and not being able to look things up, distorts the code. I am aware that the code I wrote was ugly, especially the atrocious casting of the number to a string, then to a sequence of Characters, then back to a string, and then back to an integer. But now that I am back in the real world, and able to do a Google Search, a quick search brings a top result that points me to an example by Saul Hazledine:
(map #(Character/digit % 10) (str number)))
So, in the real world, that is what I would have gone with and my code would have looked a bit better.
I guess there are engineers who stay calm in situations like this, and I assume this must be true of all the programmers who got hired at the place. I guess this is a bit like the situation in basketball, do you panic when a defender is faster and taller than you, or do you stay calm and pass the ball in an intelligent way. These tests perhaps bring out how I write code under pressure, like if its the end of the sprint and I’m working on a task that absolutely has to go out during this sprint, though hopefully such crunch-mode programming makes up less than 5% of our programming, since this is when the worst kinds of “technical debt” gets created. Or, possibly, tests like this allow them to hire those programmers who never create “technical debt” no matter how much pressure they are under — but I would be skeptical of that claim.
[UPDATE] David Tuite writes:
Expressing confusion in an interview doesn’t make you appear dumb. In reality it’s quite the opposite.
I would take that advice with a grain of salt. Some interviewers are good and some are bad. David Tuite’s advice is common, but sometimes it is wrong. Multiple studies show that your questions can have a subconscious effect on the person interviewing you. Even if they say “Please feel free to ask questions” if you phrase the question the wrong way, or ask a question outside the bounds of what they were expecting, it becomes a mark against you. The effect can be subconscious, but the bias will cost you a job. When you are in the presence of someone who really wants you to ask questions, that typically becomes apparent after a few minutes of talking to them, and in those cases you can feel free to ask what’s relevant — however, it is unwise to start with the assumption that the person who is interviewing you will automatically give you the benefit of the doubt — it is much wiser to start off cautiously and figure out who you are talking to, before you ask too much.
[ [ UPDATE ] ]
February 8, 2022 9:33 am
From Michael S on How I recovered from Lyme Disease: I fasted for two weeks, no food, just water
"Did you have Bartonella, too? Seems it uses autogenesis..."