A good measurement of a person’s expertise in a programming language is the understanding of fundamentals of this particular programming language.
Get the fundamentals down and the level of everything you do will rise.
You might think that this is the first things you learn when picking up development, but for most developers, understanding the building blocks of a programming language, especially when it is the first one you learn, is something you usually learn after having experience with building things in “oh, that’s why!” moments.
I would argue learning those principles later on is not the best way to learn programming and I would advise new developers to learn them as fast as possible, but as it is the reality for a lot of people, and I think will be for quite a long time, it always makes for a nice test.
Note that, this is the case even for people with academical backgrounds. Even if programmers with formal programming education profiles tend to have a better understanding of algorithmic principles, they usually don’t have much time to dig deep into particulars of a language. So their academical background doesn’t give them an unreasonable advantage when passing this type of test.
I will use Javascript to illustrate this example but you can come up with those types of tests in pretty much any language.
If you are not super technical, you can ask a person with senior programming skills in the language you are testing for to help you and they will come up with ideas in no time.
The problem at hand:
Here is our code snippet:
1["1", "2", "3"].map(parseInt); // [1, NaN, NaN]2// This is not what we want nor expect. We expect this line to return3// [1, 2, 3] but for some reason, this is not what happens here.
In case you don’t know, “map” will run the function you provide as its first argument on each element of the array and return a new array containing what your function returned when called with each particular element, in place of the original element.
So, the “parseInt” function, which is meant to transform a string of characters into the corresponding integer, will be called on each individual element of the array (which are strings and not integers at that point) and return the corresponding integer.
Although, as you can see, this doesn’t work at all in this example, and except for the first item, we always get NaN as a result.
What are we testing ?
As with the previous blog post in this series we are going to ask “Why does it behave like this?” and NOT to just fix it.
Here, we are hoping for two possible positive outcomes:
The first thing we hope to see is that the candidate can see the problem in the blink of an eye because he/she knows the fundamental flaw in the code from understanding the principle (which in most case is a good signal of seniority) or at least figures it out quickly enough and explains the problem by using the principle as an explanation.
If it is not the case, we will observe if he/she plugs everything together and comes out with the principle behind the problem without resorting to magical thinking, which will probably trigger either a smirk or an “aaahhhh”.
Again, if given enough time this problem is pretty easy to solve. But solving the problem is not the point. We want to see how the candidate thought it through, how much hypothesis was involved, and how many failed attempts it took (read: how many times the candidate went “I know!” and tried something that didn’t work. Do not consider logging things a failed attempt.).
In the same line of thought as the previous blog post, this is about putting the candidate on a “ladder of seniority” and is never meant to be a pass/fail test. If the problem gets bulldozed in 0 seconds, either some amount of seniority is involved, or the developer has already seen this particular problem before and will reason from analogy instead of basing the answer on knowing the programming principle (which can happen, but it’s not a bad indicator either. It means the dev has experience.).
The more time it takes, the more you go down the seniority ladder.
Still, remember to not use a single test as a reliable technical interview. More tests equals a more precise result.
The principle behind it:
The principle here is simply that in Javascript, you can declare functions that expect other functions as parameters, which they will in turn use to do what they have to. Let’s call those functions that expect other functions as arguments “higher order functions” here.
When an higher order function uses one of the function you provided it, it calls it with a defined set of arguments, so the function you provide must be “compatible” and work with those pre-defined arguments.
In our example here, “map” expects a function as its first argument and expects that function to take at maximum 2 parameters.
It will always use it by giving it those parameters: the first will be the element “map” is currently processing (1, 2, 3 in our example), and the second will be its position in the array (0, 1, 2).
It matters very little if the developer knows the math principle here, the only thing that matters is to figure out that “map” passes 2 arguments to “parseInt” and that the second argument to parseInt changes something that breaks the logic, but here is the completely explanation:
The function “map” calls “parseInt” with parameters “1, 0” then “2, 1” then “3, 2”. The problem here is that the second parameter of Javascript’s “parseInt” function is the radix, the “base” with which we are counting numbers.
For the first element, parseInt ignores “0” and falls back to its default radix value (10) so it works as expected.
For all other elements after the first one, parseInt will receive a valid argument as the radix and will try to “count” using that number. So it will try to “count” to 2 in base 1 and 3 in base 2.
If you know a bit about maths, you know that you cannot count up to a number using a base that’s inferior to it.
Solution:
There is only one solution here: make sure “parseInt” is called without a second argument, or make sure this argument is always 10.
You can write this in several syntactically different manners, but it’s always the same logic.
1) Declaring an inline arrow function, ignoring the second argument:
1["1", "2", "3"].map((value) => parseInt(value)); // [1, 2, 3]
2) Fixed “10” as second argument:
1["1", "2", "3"].map((value) => parseInt(value, 10)); // [1, 2, 3]
3) Declaring an external wrapper function around “parseInt”:
1function parseIntBase10(value) {2 return parseInt(value, 10);3}45["1", "2", "3"].map(parseIntBase10); // [1, 2, 3]
Etc, etc…
Why is it important to know things like this ?
There are two main reasons:
- You would never write a piece of code with this kind of bug in it. Here the example is pretty obvious, but you can imagine a scenario with other similar logic that would break given only certain arguments as the second one and end up randomly exploding in production. There are a bunch of problems you can stumble into if you don’t understand the tools you are using. You are going on an highway without knowing how to use the brakes.
- Knowing the fundamentals behind tools you use allows you to write more elegant, shorter, better code. You know how to piece things together, make seemingly technically challenging functions in a few advanced, apparently simple programming statements because you are aware the shortcuts you can take, and the things to avoid.
If you want to learn more tips and tests for your technical interviews, here is the complete list of the articles in this series of posts: