Saturday, January 12, 2008

How to Recognize a Good Programmer

I just recently interviewed for two jobs. I did this mostly for "practice," just to somewhat test how my resume stands up, what interview questions are common, etc. I'm sorry to say (actually, not very sorry at all) that I was not offered a position at either of the two jobs. Actually, I am a little disappointed that I didn't get an offer, because I tend to believe that I have a decent resume and background experience that would likely benefit most any technology hiring company.

Before I continue my posting (below), I'm including a link to an inspired article I just read called "How to Recognise a Good Programmer" (note the UK English spelling of recognise). This is, in my opinion, a must read for hiring a programmer.

It seemed in my two interviews, the basic deficiency I had was that the language I touted to be strong at (Java), I was not able to quickly identify common coding errors in printed source code, or not able to describe an ideal algorithm to solve grossly complex unrealistic problems.

In the first interview, I got the typical "gang up" group interview by fellows who would be my peers, but also from a couple of supervisors. The group of four basically groped me for the typical interview questions about my background, etc. They even got to asking some very specific technology questions (like Oracle specific questions), which was OK but a little overboard.

Of course, after the group grope, they left me with the staff "Java guru" who would "grill" me on Java for a "couple of hours." He gave me a test to fill out and said he'd be back in 20 minutes. The test was very long, more like a 2 hour test, and he returned in 10 minutes instead of 20. Needless to say, I didn't have very much of the test done.

The first questions were meant to "trick" me. You know, like with obscure syntax, or places where an array would be indexed out of bounds. For example:


String s[][] = new String[2][2];
...
s[1][2] = "foo";


Of course, the problem here is that index 2 is not valid for the array. But, if you're looking at tons of code and the question is asking you generically "what will the output be," you might very easily overlook this problem. The brain doesn't immediately recognize that I'm trying to access something outside of the defined parameters.

A good programmer would go out of his way to ensure that this scenario never occurs. If I explicitly define an array, I'm quite consciously going to remember not to exceed the array's limitations. Or, if I knew that an array out of bounds problem was likely, I would choose a different data structure entirely.

The fact that I "missed" this question (and others like it) tells me two things: a) I obviously rely too much on good coding practices and/or good IDEs (like Eclipse) to help avoid these types of problems, such that I don't intrinsictly recognize the problem just simply by looking at some printed code; b) I obviously need to practice a little more in reading through other people's poorly written code, as I likely have too little experience fixing other people's code.

The second interview was a bit more classy, though I still came away with the same thoughts. The first question was "write code to take a string and remove the vowels from it." We were using a whiteboard (not a traditional development environment).

My gut reaction to this is, you've got to be kidding me. Of course, there are likely 1000 different approaches to this problem and the interviewer is looking for the "right" solution.

Maybe thinking too "low level", I simply create an array out of the string, iterated through the array and pushed the non-vowels into a new string. A perfect solution in C, for example. No no, in Java we have many other ways to do this, including regular expressions, substring matching, using collection comparisons, etc. Fundamentally, however, at the "low level" these higher level methods do exactly what my solution did. I'm half tempted to benchmark my low level solution, just to give me some satisfaction that it would have potentially beat the "high level" (fewer lines of code) solution the interviewer had in mind as "correct."

Short simple elegant code is great. KISS is my motto. His preferred solution (using regular expressions) was of course fine, simple, easy to use, elegant. However, that doesn't mean that the one I came up with, without any help of an appropriate Javadoc, isn't as good in certain contexts as his.

Apparently for question 2, I "nailed" it. In his second scenario, I was to write (on the white-board) a number generator that randomly dispersed integers with a minimum, maximum and average, such that at the end of a variable number of iterations, the actual average of the numbers returned matched closely with the supplied desired average.

I honestly didn't think this was too hard of a problem, but apparently everyone else he interviewed struggled with it. I simply generated a random number either above or below the desired average depending on the current running average. If my current running average was below the targeted average, I would return a random number between the target and the max. Over a lot of iterations, this algorithm obviously produces numbers that will come very close to the target average.

Ya, so like I said, I "nailed" this solution. He said he was confused because he couldn't understand how I had flubbed the first question so bad and answered the second one so good. I was confused as to what he was confused about, as both my solutions (to me) seemed fine and appropriate given the lack of any more context.

So, given that I was now 1 for 2, he wanted to ask one more final question. Basically the scenario is that you have 100 million records each on four machines. You must sort the records and distribute them so that the A-F records land on machine 1, G-M goes to machine 2, etc. The original records are unsorted in a text file.

I honestly didn't come up with a very good solution; not especially happy with my answers here. But, in general, I guess my feeling is that for a problem like this, I would likely be writing the code multiple times isolating the bottlenecks along the way instead of trying to over optimize a certain potential problem without having the statistics to back whether it's a problem or not.

I suggested that I would like to create some temporary files on the local machines, such that all the A records were in a temporary file. Every machine would end up with A-Z temporary record sets, which would then be transferred over to the other machines (using traditional file transfer protocols (as opposed to Java based sockets)).

No no, that's no good (apparently) because we are using the hard drives and not using the network in the first phase, and only using the network in the second stage. How can we saturate both the network and the disks at the same time? Well of course, my answer, we break these temp files into smaller chunks and spin off additional units of work. Thus, we can optimize how big the temporary files are versus the network latency.

No, this is still no good (apparently). What he's really wanting is to not write temporary files at all and just parse the main 100MM records and send the individual record on to each machine directly.

I about fell over. Wha?? You're kidding, right?

The most positive thing I could say to that was "my gut reaction tells me that sending a single record at a time over the network is not as robust or performing as sending a larger set, say 10,000 at a time). I'm feeling much more comfortable in sending a smaller amount of larger data than a larger amount of smaller data. He's suggesting to send 100,000,000 x 4 machines individual TCP records across the network.

Just the simple TCP overhead of this seems pretty silly in my mind. In this scenario, I would much rather completely stuff full a TCP packet payload than to send a small packet through. I just don't "get" what he thinks is ideal.

Further, I apparently didn't address any of the nuances of reading from the disk, and sending the packet on the network. That is, I didn't specify that I would "buffer" the disk reads in any way, and so my "architecture" wasn't the best. For example, maybe the network is slower than the disk, and as such, now my disk read is blocking on the network call.

Well, first of all, I half assumed that using a BufferedReader would be appropriate, and yes I didn't specify that. But secondly, it doesn't really matter if the network is slower than the disk and the disk has to block, because fundamentally the network card is serializing outbound packets at the hardware level. It's not like the network card is going to send any faster than it can just because we have buffered our disk reads.

I think ultimately he wanted me to read a record from disk, evaluate which machine to send it to, put the record in a temporary buffer, and have multiple threads pulling from the buffer to send out to the appropriate machines. I.e. a straight producer/consumer type pattern. I can agree with this, seems like a logical thing to do.

But, I guess it's just the approach that I take issue with. It's not like I can argue with him about the fact that sending smaller packets has more overhead than sending larger packets. In his mind, there was no additional overhead associated with this. What he really wanted was for me to talk about what happens between the disk read and the sending of the network packet. How about just stating that's what you're wanting to talk about instead of leaving me out focusing on other areas?

Again, this is just a stupid question in general, because I can pretty much guarantee that some bottleneck would be found that wasn't even discussed or perceived during this little "exercise."

A long blog posting, I know. Sorry. I guess the point is simply that the above article got me reflecting on my recent job interviews and how I believe I am a "good programmer" by definition of the article. The challenge for me, as I refine and add to my resume and experience, is to be able to portray that I'm am indeed a "good programmer."

0 comments: