The Programming Interview from Hell
pythonforengineers.com
pythonforengineers.com
I was ushered in. The Guy with Two PhDs (he showed me his business card first, and there were indeed two PhDs on it) asked me:
"What is the simplest way to synchronize two threads?"
I rattled off some synchronization primitives. Semaphore. Critical section. I was ready to do an implementation if he wanted.
"No, the simplest."
I dug around. Interlocked operations? A mutex? A spinlock? I mentioned Dekker's algorithm (it sucks, but it's simple). I dredged up a few more.
"No, I want the SIMPLEST possible way to synchronize two threads. What is it?"
I'd run out. He gave a disgusted snort. The next question wasn't much better: "What's the BEST way to share data between programs?"
"Not sure what you mean by best. How many programs? Is it over the network? Hmm, shared memory and a maybe a signal of some kind?"
"No, the best way!"
This interview did not go well. At the end, Dr. DoublePhd scolded me for dropping out of college and told me to go back to school to finish my degree.
That evening I wrote the hiring manager that Dr. DoublePhd appeared to want the answer "raise interrupt priority" for the thread synchronization question -- which doesn't work on a multiprocessor -- and that I had no idea what the heck he was asking for on the other questions.
I didn't add that even the awful questions I was being asked could have been productive interview fodder in the hands of a good interviewer, but that in the hands of a terrible person they were destroying that company's ability to hire.
Turns out that I didn't need to add that last bit. A few months later the hiring manager emailed me, saying that they had fired Dr. DoublePhd and would I consider interviewing again? I politely declined.
I never found out the BEST way to share data between programs. In fact, I'm still looking. I think we all are.
"Here, run this unsigned code for me in your head." That has all kinds of possibilities . . .
[See John Barnes' book _Candle_ if you want some scary prognostication. Warning, it's kind of graphic and disturbing in spots]
Come on, add another layer of indirection! When given a problem to solve, hand back another problem.
In this case:
Q: "I want the SIMPLEST possible way to synchronize two threads. What is it?"
A: The SIMPLEST possible way to synchronize two threads is a global maximal element (not necessarily unique) of a poset induced by a partial ordering on possible methods for synchronizing two threads. If the induced poset is totally ordered, and assuming that at least one possibly way of synchronizing two threads exists, then there exists a SIMPLEST possible way to synchronize two threads. If you hand me a method for comparing two synchronization methods for SIMPLICITY, then an algorithm retrieving such a best possible solution is trivial, but at this point you must define define what you mean by SIMPLEST before we can move any further.
Mr. Double PhD was trying to be clever.
Copy-paste.
Btw = if you're worried about conflicts - it's Ok there's a second file called "writeStatus.txt" which you have to claim by replacing the String NULL with your processId - thereby claiming write access to data.txt and causing any other processes to Thread.Sleep until it's free.
Unrelated, who's hiring...
>Copy-paste.
You may joke, but it's the most universally supported, and therefore the most likely to be available.
Of course it fails if you're working in user mode. Or you have non-maskable interrupts that you need to sync with.
In this case the poor PhD x 2 had been working for years on consumer PCs, which were at the time nearly all single CPU. I'd been mucking about with multiprocessing a fair amount, and the IRQL thing never even occurred to me during the interview. Just as well.
We ended up hiring him, as we'd interviewed 10 other candidates who'd failed at that point.
He ended up being a terrible employee.
Seriously, when someone asks you the details of a linked list, they're not trying to find out if you will be able to use one specifically on the job. They're trying to figure out if you know how to implement the details of a moderately tricky algorithm/data structure.
I've never implemented a linked list for work, but I have implemented complex data structures and other analogous code. Implementing a linked list demonstrates that I paid attention to the basics and can reconstitute mildly complex algorithms when needed.
[0]: https://github.com/buserror/rf_bridge/blob/master/src/rf_bri...
There are many people for whom this is complex, you're assuming a foundation that not everyone has. There are also many people for whom nothing is complex, they assume they can understand everything, while they don't currently, they assume they'll be able to learn it without issue.
Dealing with new starts who are straight out of education is often like reading posts from 4chan.org/b, at first you don't know if they're joking.
It's not all doom and gloom, occasional I'm pleasantly surprised by the calibre of those beginning their career/hobby, but this is the exception.
I agree, its not that hard, but that's all the more reason to avoid giving attitude about the question. Answer it quickly and move on. If you can't answer it quickly, maybe it _is_ that complex.
A lot of interviewers have a short time window in which to conduct their 1-on-1. Sometimes they ask easy questions for a reason.
Often, I start with a soft ball that I intend on building on - turn a linked list into a doubly linked list; a circular list; can you improve the lookup time; can you make it generic; what are the space constraints; what are the time constraints.
And if they can't answer the simple question, we just leave it at that.
A simple question can easily be built upon. "I'd Google it" can not.
1. Write a function that determines if a number is prime. If they didn't know what a prime number was, we would tell them.
2. A simple problem that required designing a database schema and sql query that involved a left outer join.
There were a lot of developers who couldn't do it.
On the other hand, there was one developer who was just learning c#, who had spent most of his time doing VB.net, couldn't answer a lot of the technical questions but we could tell by his thought process and how he explained real world problems he solved that he would be a great asset to the company. We fought for him over more "senior" developers.
When I have a chance to hire again, I'm going to fight to get him -- even though he sucks at interviewing and i might have to do a little convincing.
Did you also give them an algorithm to implement or was a brute force method good enough?
1st level optimization: skip even numbers.
2nd level optimization that I was the only person to get when I had to interview for the company: loop starting at 2, and go the square root of the argument.
That depends on the language. If you're using something without pointers or references it's quite hard.
They all use them internally, but don't tend to make them available to the programmer (usually because they aren't needed).
Behind the scene, yes, every Python variable is a reference to an object. It's not addressable, however, and in the case of immutable objects (like strings), you can't modify the underlying object and keep all references pointed at that updated object.
...unless you need them for lock-free programming. There was a nice C++ talk by Herb Sutter on that, with the apt title "Juggling Razor Blades". That pretty much says it all.
From a practical standpoint, the question "what is a linked list good for?" really might be more interesting. Does anyone know of potential use cases besides kernel design, lock free programming or Clojure-style immutable datastructures? I'd guess CPU caches to have erradicated most of them...
Few questions I usually ask:
- How can you make sure that your Java application runs on the server not only on your laptop? (I take any answers: containers, single JAR, etc.)
- What is printed out
def add_list(val, list=[]):
list.append(val)
return list
print add_list(10)print add_list(20)
print add_list(123,[])
- Explain recursion
Funny to see how a non-trivial amount of programmers fail to answer these questions.
Anything less than tossing the playbook warrants a gentle suggestion that we shouldn't waste any more time here and why don't we just end the interview. Stand up, shake hands, thank them for their time, and walk out.
Problems in the interview process should be seen as problems with company culture. I used to wonder about how to appropriately answer the question, "which companies are worth working for?" because it seems like you need a lot of time before you can really tell. But once I realized that the interview is just an extension of company culture, it got a lot easier to weigh opportunities.
I mean, obviously, if you need the money you need the money, but developers are hot enough commodities that it doesn't take long before you're entrenched enough to be able to call the shots like that.
A while ago I was invited at a startup for a casual discussion about an open position they had (front-end developer). I went because I knew several employees and they were nice. The casual discussion turned out to be several questions about UDP/TCP and writing algorithms on a blackboard (counting leaves in a tree, finding is a word is an anagram, etc.). Nothing really hard, but quite disconnected from the day-to-day job of a front-end developer.
Write a function which takes as input a list of sets, many of which are not disjoint, but will output a list of sets where all of the non-disjoint ones have been merged back together again. So the output is a list of sets which are all disjoint from each other because any intersecting sets have been merged together.
e.g. given the input:
[(1,2,3), (2,4,8), (10,11,12)]
it will report back: [(1,2,3,4,8), (10,11,12)]
because (1,2,3) and (2,4,8) are not disjoint, but the (10,11,12) set is.Whereas given the input:
[(1,2,3), (2,4,8), (10,11,12), (8,10)]
it would report back a single set: [(1,2,3,4,8,10,11,12)]
because now all of the sets are connected - the 8 and the 10 now connect everything else together.I came up with an algorithm which is acceptable for the dataset we currently have - but I've no idea what time complexity it is (for our real dataset it was able to do it in "one pass" - but in principal it could be worse than that). I don't know how I would implement a distributed version if the list of sets was too big to fit in memory, etc. etc.
And I haven't found a good solution on Google (but I'm not even sure what to Google).
1 - 2 - 3
\
4 - 5
Building the (undirected) graph would take linear time, and once it is built, you can do a simple Depth First Search to mark all the connected components.For i from 0 to n-1 , find all sets from i+1 to n-1 which have a non empty intersection with set i. Union set i with all those sets and replace i with the union set.
If you use a disjoint set data structure this will be quadratic or O(n^2)
EDIT: On further thought you need to merge from the end and backwards.
Just add everything one by one to a Disjoint set union.
My bad.
The complexity of this data structure is pretty interesting. It basically comes to O(N) for N < any number that can be represented in the known universe. It's also the coolest use of the inverse ackermann function I've seen!
How to solve this on a distributed system I have no idea.
[1] https://en.wikipedia.org/wiki/Disjoint-set_data_structure
class Ptr {
public Ptr next;
}
<T> List<List<T>> mergeIntersecting(List<List<T>> lists) {
Map<T, Ptr> lookup = new HashMap<>();
Map<Ptr, List<T>> output = new HashMap<>();
for (List<T> list : lists) {
Ptr ptr = new Ptr();
if (list.isEmpty()) {
output.put(ptr, new ArrayList<>());
}
for (T value : list) {
Ptr prev = lookup.get(value);
lookup.put(value, ptr);
while (prev != null && prev != ptr) {
Ptr tmp = prev.next;
prev.next = ptr;
prev = tmp;
}
}
}
for (Map.Entry<T, Ptr> entry : lookup.entrySet()) {
Ptr ptr = entry.getValue();
while (ptr.next != null) {
ptr = ptr.next;
}
if (!output.containsKey(ptr)) {
output.put(ptr, new ArrayList<>());
}
output.get(ptr).add(entry.getKey());
}
return new ArrayList<>(output.values());
}
If I give it random lists of integers, it takes around 1 microsecond per element of input. Really curious if there's any way to speed it up a lot.Here's a test implementation, assuming input is one line per input set with space separated values on each line. Output format is the same.
#!/usr/bin/env perl
use strict;
my @in;
my @out;
my %sawin;
my %merge;
my %merged;
while (<>)
{
chomp;
s/^\s+//;
push @in, [split /\s+/];
}
for (my $i = 0; $i < @in; ++$i) {
foreach (@{$in[$i]}) {
if (defined $sawin{$_}) {
$merge{$i}{$sawin{$_}} = 1;
$merge{$sawin{$_}}{$i} = 1;
} else {
$sawin{$_} = $i;
}
}
}
foreach (my $i = 0; $i < @in; ++$i) {
my %out;
next if $merged{$i};
$merged{$i} = 1;
$out{$_} = 1 foreach @{$in[$i]};
foreach my $ms (expand_merge($i)) {
$merged{$ms} = 1;
$out{$_} = 1 foreach @{$in[$ms]};
}
push @out, [sort {$a<=>$b} keys %out];
}
foreach (@out) {
print join(" ", @$_), "\n";
}
sub expand_merge
{
my($base) = @_;
my @todo = keys %{$merge{$base}};
my %done = ($base => 1);
while (@todo) {
my $next = shift @todo;
next if $done{$next};
$done{$next} = 1;
push @todo, keys %{$merge{$next}};
}
return keys %done;
}
Everything should be linear in the total number of elements except for expand_merge (the flood fill-like part). I think worst case for expand_merge could be quadratic in the number of elements, which would occur if each set overlapped a large fraction of the other sets.If things won't fit in memory, I don't know how to do it in the general case. I suppose the first thing I'd do is look at the source of the sets to see if there are any limits on that. For instance, if we are dealing with a very large number of sets without a lot of members per set, and the range of numbers in each set is not very large, then it should be possible to partition the input into two sets of sets, A and B, such that it is easy to show that no sets in A contain any overlap with any sets in B, so we've reduced the problem to two smaller problems that can be solved independently and their outputs concatenated. Repeat.
For the general case, I'd start out by sorting the elements of each set, and by sorting the set of sets. While Googling for a refresher on external sorting and then coding up that part, I'd be hoping for some flash of brilliance to deal with what to do after that.
If no flash of brilliance arrived, I'd probably try something like this (assuming that I can at least fit several of the sets into memory at once). Let's assume that each set is stored in a file, named after its order in the sorted list of sets.
Read the first set into memory. Then scan through the remaining sets, in sorted order, checking each for overlap with the first. For any that overlap, merge them in memory with the first. When all the sets have been processed, or a point is reached where the first element of the current set is larger than the last element of the merged first set and so you can infer that no more merging will happen on this pass, write the merged first set out, replacing the original first set, and delete the files for all the sets that merged with the first.
Repeat this until no new sets merge with the first. At this point, you can mark the first as done, and it becomes the first output set.
Repeat with the first remaining set as your new first set, and so on.
As long as the biggest single output set and the biggest single input set will both fit in memory at the same time, I think that the above approach works.
I have a feeling that there is some clever way to do this that is much more efficient and is much more obvious (in the mathematical sense...in other words, after you look at it for a very long time and think about it really really hard it was clearly obvious).
My guess is that the clever solution will heavily involve sorting...not that I'm really going out on a limb with that guess, because almost everything is sorting when you look at it right. For example, here's a shell script that given a list of x, y coordinates on STDIN (one coordinate pair per line, x and y separated by space) outputs the result of doing one generation of Conway's Life with the input being the initial cell configuration:
> alive.$$
while read cells
do
echo $cells >> alive.$$
set x $cells
x=$2
y=$3
echo $x $((y-1))
echo $x $((y+1))
echo $((x-1)) $((y-1))
echo $((x-1)) $y
echo $((x-1)) $((y+1))
echo $((x+1)) $((y-1))
echo $((x+1)) $y
echo $((x+1)) $((y+1))
done | sort | uniq -c > neighbors.$$
grep '^ *3' < neighbors.$$ | sed -e 's/^ *[0-9].//'
grep '^ *2' < neighbors.$$ | sed -e 's/^ *[0-9].//' > has2.$$
sort alive.$$ -o alive.$$
comm -12 has2.$$ alive.$$
rm has2.$$ neighbors.$$ alive.$$
Note that the key operation is "sort". This runs in O(n log n) where n is the number of live cells (assuming your Unix uses an n log n sort...).Input: one file per set, with names of the form set.X. Format of the file is one value per line. E.g., the set (1, 2, 3) might be in file set.0 with contents
1
2
3
Output: each run of the script will merge overlapping set.X files, deleting files that are made redundant. It will tell you how many sets were merged.Run the script repeatedly until it says "merged 0".
#!/bin/bash
for i in set.*
do
sed -e "s/$/ $i/" < $i
done | sort -k 1 -n > m.$$
last_val=-1
last_set=
merged=0
while read in
do
set x $in
if [ $2 -eq $last_val ]
then
if [ -f $3 ]
then
cat $last_set $3 | sort -n | uniq > t
mv t $last_set
rm $3
merged=$((merged + 1))
fi
else
last_val=$2
last_set=$3
fi
done < m.$$
rm m.$$
echo merged $merged
The above does more passes over the complete set of elements than is necessary, in order to minimize memory use. At the cost of a little more memory, it could write the commands done in the while loop (cat|sort|uniq;mv;rm) out to a file, and then edit that file to adjust it to take into account the affect of the rm's, and then do one pass of merging.That would look something like this. First, you'd run this script once:
#!/bin/bash
for i in set.*; do sed -e "s/$/ $i/" < $i; done | sort -k 1 -n > m
last_val=-1
last_set=
line=2
> s
while read in
do
set x $in
if [ $2 -eq $last_val ]
then
#echo "if [ -f $3 ]; then cat $last_set $3 | sort -n | uniq > t; mv t $last_set; rm $3; fi"
echo "cat $last_set $3 | sort -n | uniq > t; mv t $last_set; rm $3"
echo "$line,\$s/$3/$last_set/g" >> s
line=$((line + 1))
else
last_val=$2
last_set=$3
fi
done < m > c
That gives an output command file, c, that looks like this: cat set.4 set.7 | sort -n | uniq > t; mv t set.4; rm set.7
cat set.0 set.5 | sort -n | uniq > t; mv t set.0; rm set.5
cat set.1 set.5 | sort -n | uniq > t; mv t set.1; rm set.5
cat set.5 set.7 | sort -n | uniq > t; mv t set.5; rm set.7
cat set.2 set.6 | sort -n | uniq > t; mv t set.2; rm set.6
cat set.3 set.6 | sort -n | uniq > t; mv t set.3; rm set.6
Note the problem with this. Line #1 removes set.7 after merging it with set.4. But line #4 refers to set.7. Since 7 was merged into 4, it needs to refer to set.4 at that point, not set.7.The script that made c also outputs a file, s, with sed commands to do the above fix. For the above example, it looks like this:
2,$s/set.7/set.4/g
3,$s/set.5/set.0/g
4,$s/set.5/set.1/g
5,$s/set.7/set.5/g
6,$s/set.6/set.2/g
7,$s/set.6/set.3/g
There is still a problem, because note that s suffers from the same problem that c does! Line #4 of s also refers to set.7, but at that point it should be set.4.So, before using s to fix s, we have to use s to fix s: "sed -f s < s > s2", giving this for s2:
2,$s/set.7/set.4/g
3,$s/set.5/set.0/g
4,$s/set.0/set.1/g
5,$s/set.4/set.0/g
6,$s/set.6/set.2/g
7,$s/set.2/set.3/g
In this case, that is sufficient. We could now "sed -f s2 < c > c2" and then "bash c2", and we'd be left with set.1 and set.3, with the other sets properly merged in.However, in more complicated cases one application of s to itself is not always enough. What we really should do is keep applying it to itself until we hit a fixed point, so "sed -f s2 < s2 > s3" giving:
2,$s/set.7/set.4/g
3,$s/set.5/set.0/g
4,$s/set.0/set.1/g
5,$s/set.4/set.1/g
6,$s/set.6/set.2/g
7,$s/set.2/set.3/g
and if you them apply s3 to itself, you will see that there is no change, so s3 is our fixed point. We could then "sed -f s3 < c > c3". Turns out that c3 is identical to c2, so we get the same results as earlier.For context here, I'm a college dropout. I made it through two years before I discovered that I could skip right to having a job, and for where I was in life, it was the right decision for me. But seriously, what's the deal with this hatred of "Big O" notation?
This isn't some cryptic language only for use by CS PhDs. It's not even a concept that's hard to explain to someone who isn't in our industry!
"The question we're trying to answer here is, how does our solution scale as the problem gets bigger? Are we able to solve it in the same amount of time regardless of size? Does our solution take more time as the problem gets bigger, but only in proportion to how much bigger the problem is? Or does our solution take much, much longer as the problem gets bigger, to the point where it's no longer feasible to solve?"
Asking a candidate to be able to have a conversation about this -- how do time and space complexity scale -- is not unreasonable. This is the difference between designing a workable solution early, and discovering that what worked great in a unit test and some one offs behind the scenes fell over in production.
Similarly, I see no problem in expecting a candidate to have some familiarity with the basics of simple data structures. I don't generally rewrite them either! But I use data structures all the time, exposed to me via the standard libraries of programming languages that I use, and if I don't broadly understand how they work, when they are appropriate and when they're not, and when one choice is superior to another, I'm not doing my job.
I agree that in a lot of instances, Google is the answer. But Google is of no help if you don't know what to ask, or worse, if you don't even understand that there's a question to ask.
I'm not defending the truly awful interviews that I believe really do happen. But let's not pretend that it's not important to understand how these details work to get proficient, or even adequate, at our jobs.
I was once interviewing for a C++ position and the interviewer presented me with some C code with a broken "swap" implementation and a driver function and asked me to fix it. I simply prefixed the call to swap with "std::".
He wasn't very happy about it. ;-)
Can you imagine interviewing someone to build you a house and spending 90% of the interview time asking him how he'd chop down trees for wood and manufacture the nails? Welcome to software interviewing.
A candidate's ability to re-implement the language's standard library is not so useful if we simply use the language's standard library in our application code.
You will never be asked to reimplement a library function on your job, these questions are used to see how you approach a problem, your reasoning, etc...
It's also a lot easier to onboard new people if you're using a popular third party package and you have thousands of people that have already had the same problems you might encounter.
Seriously that's the correct answer as far as any productive engineer is concerned right?
using std::swap
swap(a, b)
/doublesmartassMy favorite interview was for an architect position, where my two future peers took turns telling me horror stories about working there, to see if I would run away screaming. I got the job and only ran away screaming 18 months later.
As an interviewer, if there was someone we liked, we were brutally honest about the company culture. We (myself and my manager) wanted senior engineers who weren't afraid to speak up and help shake things up in the larger organization. If they still accepted the offer, they were what we wanted. We had too many people quit out of frustration instead of fighting.
At another job I applied to as an architect, the manager told me all of the issues with the processes (and lack there of). He tried to "scare me away". I asked him one question. Will I be able to come in and make the changes needed and will he have my back if I inadvertently step on toes trying to do the right thing? He said yes and I accepted the offer.
"I'd google it. Anything less would be a waste of our time." That cuts through a lot of bullshit. After saying that at my last interview, we got into actual problems (structuring a program, building an API, writing code to approximately simulate real world problems, data structures). Had a good time, got hired, and loved working with those people.
On the other end of the spectrum, I've been torn to shreds in an interview for not being able to write a fast hash table implementation in Java (didn't know Java at the time and the interviewer didn't know Ruby/Python/C) in an hour of trying. That was soul-destroying.
Maybe I was just ahead of the times or they weren't looking for someone to be as honest as I was but I didn't make it past round 3 of their interviews.
new java.util.HashMap()
In case you ever get such a stupid question again."It does."
"Then I'd use that."
"That wasn't the question."
"Oh... kay..."
It's worth mentioning that this oft-repeated canard doesn't have much evidence. Manhole covers come in a great many shapes, and this has always been true: the Romans made them square. There are plenty of shapes which won't fall in, such as any curve of constant width. Basically fake-Feynman is right: manhole covers are round because it is often convenient to make manholes round.
I used to live in a town in the US where many of the manhole covers were shaped like a D.
Also, there's a difference between asking "How would you solve X (in Y language)" and expecting a line-by-line code sample. In the former you should be able to talk about how you would structure the code, what input/output you would need, how to set it up, data stores, etc. whereas the latter is (virtually) impossible without help from Google.
While it would be lovely to hire candidates by "everything it says in the resume", the truth is that only works for really great hire... which you don't know until you've talked to them about how they work, what they've done and how they like to solve problem.
We provide industry-leading compensation, but no one pays for overtime so neither do we.
The technical questions were my fault because I told the interviewer I was a researcher in number theory and enjoyed working with embedded systems.. to which they said, 'oh, can you talk a bit about that?'
Each comma denotes an email response explaining I was chosen to move on and to please fulfill the next request: Send resume with cover letter, fill out online application, make 90s video about why you should serve coffee, first video chat interview, come in to meet recruiter, come in to meet store manager.
I only made it through the first of the three interviews. Ultimately I think we both dodged a bullet there.
Honestly!
Ask an employee next time you get a coffee there what kind of interview process they had to go through.
PS- should have made a 90s '90s video about why I should serve coffee.. definitely would have gotten their minimum wage offering then.
I always find it suspect when self proclaimed awesome genius programmers take offense over simple common questions (like linked list). Had it been something esoteric, ok. But these come up so often and take so little time to learn.
He: "Hi. My name is [X] from [Y:company]. We were scheduled for an interview now."
Me: Yes.
He: "Ok. Am I audible?"
Me: Yes.
He: "Cool. [Answer this question]"
Me: baffled screeching
He talked about a lot of things then including "How does Node.js manage it's asynchronus thread? Where is the sequence of functions to run stored in the memory?". In the end I asked him what was a general day like. To which (no kidding) his reply was:(sad tone) "It's Friday 6pm. Everyone is having donuts and here I am taking this interview".
Well, if you're more concerned about your donut, thanks I guess. I got rejected after this round.
That said, being able to at least describe some use cases of some relatively simple data structure is not too much to ask.
I gave them my solution, but, well, my interesting in that company has gone.
Here's a hint - I trussed natd while it was running. Ooops.
This is his writing style, which I believe would complement yours quite nicely: https://www.reddit.com/user/commahorror
P.S : this is replied from desktop. hope this works for you