Unlocking the JavaScript Code Interview (an Interviewer Perspective)

April 15, 2018 0 Comments

Unlocking the JavaScript Code Interview (an Interviewer Perspective)

 

 

At AppsFlyer we interview quite a few Front-End & Full-Stack engineers and know the common pitfalls many interviewees struggle with. In this article, I will share a few tips from my experience interviewing candidates over the past 6 years, that will hopefully help you rock your next JavaScript coding interview.

The questions in this article are real questions I have used in many interviews, and saw multiple solutions for. They caused some distress and discomfort for some of our candidates, and from now on they will be put to eternal rest in the pantheon of interview questions.

Disclaimer: this article provides the perspective of one interviewer at AppsFlyer, exampling the train of thought during the interview process — yeah I know, these are not the only\best\most-elegant\most-efficient\perfect solutions, but this is what I have in mind when deciding if an interviewee is continuing forward with us or not.

First of all, if you were invited to the coding interview, you probably have a solid CV, so just before diving into the coding tips, I MUST EMPHASIZE — go over your CV once again, make sure your advantages and strengths POP so the recruiter notices them. By that I mean the buzz words or highlights which define your experience. The academic institutes you attended, and the great companies you worked for are obviously bold, but keep in mind we are also skimming and scanning the text, and want to see the technical buzzwords, e.g. Angular, React, Redux, Node.js, WebPack, and so on, and don’t want to see the 2006 buzz words like jQuery, Bootstrap, and so on (sorry, not sorry).

OK, you came in for an interview — welcome! One of the most important tools for finding a great developer is whiteboard\pen-and-paper coding, and like any challenge you will face, you want to be as ready as possible. Think about each of the following tools as another weapon, which will improve your backpack when coming to the interview war.

* In order to inspire you to conquer your next interview, all of the quotes in this article are from “Carl von Clausewitz: ON WAR. Book 1 & Book 6” click here for further reading on this great general and military theorist.

“War is the province of uncertainty”

Question #1: What will the following code snippet output

var a = Person('a');
var b = new Person('b');
var c = Person;
function Person(name) {
this.firstname = name;
}

console.log(a.first
name);
console.log(b.firstname);
console.log(c.first
name);

Every question you will be asked will assume proper basics, and this tricky edge case is no different. Solid understanding of JavaScript functions, objects, closures and hoisting is a must.

As for the solution:
First of all, function definition will be hoisted and it’s possible to define it after the usage (although that isn’t the best practice).
a will be assigned with undefined, as executing Person as a function returns undefined, therefore logging its name will cause an exception to be thrown: “Cannot read property ‘firstname’ of undefined”.
b is the right way, to instantiate an object and will print ‘b’ as expected.
c is just a reference to a function and will return undefined since the function has no first
name value assigned to it.

“Principles and rules are intended to provide a thinking man with a frame of reference.”

Question #2: Your input is an array containing unsorted words. Suggest an efficient solution for implementing hasWord(word) which receives a word and returns true if it is in the array and false otherwise. You are not allowed to use JS Objects (maps) in your solution but can use Arrays. Solution time complexity is more important then it’s space complexity.

In almost every question you will be asked to provide a solution for, you will be required to explain its ups and downs. You must feel comfortable with explaining computer science basics — performance of your solution, space complexity, usage of the right data structures, sorting, etc.

For this question, you will need to consider a data structure which provides the best performance for fetching and can have insufficient space complexity, with Object out of the picture.

If you were asked to find a word in an unsorted array, you can simply scan it once and return the answer — O(n). But since this operation should occur multiple times we would benefit from pre-processing the data and having each hasWord quicker.

Another solution will be to sort the Array, in n*log(n) complexity, but still, finding a word can take you an additional log(n) for binary searching the words.

Considering other data structures is another possibility. A list, stack, queue, graph all seem less relevant. Let’s consider trees, and more specifically, binary search trees, they can be implemented separately or over the array, but as we know, inserting can be O(n) in the worst case, and the same goes for search, since trees can lose balance. Their improvement, (for re-balancing) red-black trees, are too complex to implement :)

So, Let’s stick with arrays, and focus on the hint we got, space is not an issue. Let’s create a simple hashing function, which translates strings to numbers, and our array will become a HashMap.

A simple but satisfying hashing function for a whiteboard test can be achieved by translating each char to its char code, and separating them with a number which is not a char code — this will be done to avoid confusion of char separation. Yes, an efficient hashing, which is done with bit operations is not a requirement to remember and implement, and yes, this is not exactly hashing as well, as the output is not fixed in size. Let’s select ‘000’ as our separator.

Our solution will require O(n) for initial preparation and O(1) for each hasWord(). Unfortunately, our space complexity is very bad, but this wasn’t a requirement.

Here is a quick and simple implementation.

class Dictionary {
constructor(arr) {
this.dict = [];
arr.forEach((word)=>{
const code = this.encode(word);
this.dict[code] = 1;
});
}
  encode(word) {
return word.split('').map(char=>char.charCodeAt(0)).join('000');
}
  hasWord(word) {
const code = this.encode(word);
return !!this.dict[code];
}
}
“Although our intellect always longs for clarity and certainty, our nature often finds uncertainty fascinating.”

Question #3: Given a string of words, return the most common used word in it.

Well, our input is a string, and it is not a very useful data structure for counting words. Luckily, we know the string functions and can use them to convert it to a different data structure over which we can implement a solution.

The elegant solution can be implemented like this:

function mostCommonWord(inputString) {
let wordsCounter = {};
let mostCommonCounter = 0;
let mostCommonWord = "";
inputString.split(" ").forEach((word)=>{
wordsCounter[word] = wordsCounter[word] || 0;
wordsCounter[word]++;
});
Object.keys(wordsCounter).forEach((word)=>{
if (
wordsCounter[word] > mostCommonCounter) {
mostCommonWord = word;
mostCommonCounter = wordsCounter[word];
}
});

return mostCommonWord;
}

I am never looking for a one-line solution in an interview, and the same goes for over fancy styling, but I do expect a solid understanding of data structures, their functions, and a clean & easy to explain solution. I defined them as ammunition, which is a critical thing for a soldier, since they are, in my opinion, the basic building blocks for solving a problem in the coding interview.

“Everything in war is simple, but the simplest thing is difficult.”

Question 4: Write 2 functions, which receive an array of functions as an input, and notify using a promise, when they are all complete. Each of the input functions returns a promise and resolves it when done.
Your first function should execute all the functions in the input array, without any importance for their order, and the second one should execute each after the previous ended.
Both should notify its caller using a promise when they are done.

These kinds of questions, aim to explore your deeper understanding of the language and your proficiency in its main concepts, such as async, callbacks, single threaded, promises, etc.

In my opinion, these can be the trickiest to solve, since they combine logic skills and good familiarity with the language nooks and crannies.

Implementing the first function requires a little thinking, but it is very easy to execute, when you think about it. You just need to execute all the functions and wrap the result with a single promise. Actually, the Promise.all API takes care of it for you:

function executor(arr) {
return Promise.all(arr.map(func=>func()));
}

As for the second function, which requires waiting, there seem to be many ways to go about it, and you will need to think of a simple one to implement.

Await, which seems like a cool way to go, can’t be a simple solution to implement since it will wait to set values asynchronously and will not force the next function execution to be synchronous, meaning it will not wait to execute the next iteration after the current one is done.

If you want to show remarkable skills, a solution using Generators would be amazing, but not a requirement.

Here is a relatively straightforward solution which will solve the problem, using then to wait for every function to finish, and storing the results in an array.

function oneAfterTheOther(arr) {
var promise = new Promise(resolve => {
if (arr && arr[0]) {
let results = [];
internalExecutor(arr, results, resolve);
} else {
resolve();
}
});
return promise;
}
function internalExecutor(subArr, results, resolve) {
subArr0.then((val) =>{
results.push(val);
if (subArr[1]) {
internalExecutor(subArr.slice(1, subArr.length), results, resolve);
} else {
resolve(results);
}
});
}
“Strength of character does not consist solely in having powerful feelings, but in maintaining one’s balance in spite of them.”

Question #5: Implement preceding value in a LinkedList — This function receives a value and returns the preceding value in the link list.

We always strive to test each candidate purely by his or her skills, but we are all human, we are not testing a computer — we are considering a candidate.
Are they a good fit for the organization, team, group, etc.? Do I want to work closely with them on my next feature?

I am looking for a set of traits, and the best way to display them in a coding interview is reflecting your train of thoughts, and demonstrating that you are a team player.

How? When asked a question, share the voice in your head trying to crack the riddle. Like “The first thing that comes to my mind is the obvious but a non-efficient solution which is…”, “the next thing I would like to tackle is this loop which seems very inefficient”, and so on.

Another very important tool is creating a discussion. Before jumping to a solution, ask the right question. Verify you understand the questions, and what is most important to take into account in the solution (e.g. space or time). Yes, there are also wrong questions. Don’t just ask, ask relevant questions which exemplify your understanding.

One thing I can’t emphasize enough is TESTING. You wrote the code — great! You think it should work — Amazing! Now please please please don’t say: “I am done!”. After writing you code, show the interviewer that you are testing it. Take a few examples of inputs, one simple and naive, and few other edge cases, and in 90% of the times you will find some fixes and improvements you can make. Doing so shows the interviewer you will not be rushing to push half baked code to production.

Back to the question, before starting to code, I would ask my interviewer: 
Would you like me to implement a linked list in JavaScript? (Yes
What should I return if the value was not found? (return -1
What should I do if the value exists more than once? (return first)
What should I return if the value was the first? (return null)
Will it be reasonable to keep a link to the previous element, aka doubly linked list? (No)
And so on…

A great implementation can be like:

class Node {
constructor(value) {
this.value = value;
this.next = null;
}
setNext(node) {
this.next(node);
}
}
class LinkedList {
constructor(node) {
this.first = node;
this.curr = node;
}
getFirst() {
return this.first;
}
insert(node) {
this.curr.next = node;
this.curr = this.curr.next;
}
precedingValue(val) {
if (this.first) {
return this.precedingValueInternal(this.first, null, val);
}
}
_precedingValueInternal(curr, prevVal, requiredVal) {
if (curr.value == requiredVal) {
return prevVal;
} else if (curr.next) {
return this.
precedingValueInternal(curr.next, curr.value, requiredVal);
} else {
return -1;
}
}
}
“With uncertainty in one scale, courage and self-confidence should be thrown into the other to correct the balance. The greater they are, the greater the margin that can be left for accidents.”

I hope your toolkit is now a little more ready for battle.

Conquering the coding interview requires skill, and skill requires learning & training. Think about your skillset as something you should always work on, continue improving, and growing. In the time new technologies and frameworks are presented frequently, and language specification change and improve rapidly — a good developer needs to continue reading and learning — working with the same old tools for a long while makes you, in many cases, irrelevant.

In future articles, I will try to shed some light on other important parts of the developer interview journey (designing a system, logical questions, etc.).

I hope this article provided you with some great tools. Most importantly. I hope you will remember to continuously improve your toolkit!

“A conqueror is always a lover of peace; he would like to make his entry into our state unopposed”

Good luck in conquering your next challenge!


Tag cloud