If you are a Medium member please support my publication by reading it there: https://medium.com/itnext/a-javascript-interview-coding-exercise-11b801d824ec
Let’s not waste any time, in my opinion, a good coding exercise starts simple and gradually gets harder with follow-ups so that even juniors can leave a 20–30-minute coding session with a sense of achievement. If they finish only the base challenge, then it ends there.
/**
* Provide a solution for the following problem:
* You have an array with string values, that's the Source of Truth Array SOT_ARR (feel free to rename it).
* You have several arrays of strings with unknown elements, see a few examples like input1 and input2.
* For each input array produce an output array that contains only elements present in SOT_ARR
*/
// EXAMPLE VALUES
const SOT_ARR = ['SSS-Class Suicide Hunter', 'Return of the mad demon'];
const input1 = ['Designated bully', 'SSS-Class Suicide Hunter'];
const input2 = ['One Punch Man', 'bread', 'Eminence in Shadow', 'Overlord'];
Solution 1
In a coding interview, it’s best to ask questions. If you have questions on desired memory/CPU performance, time complexity of the solution or anything, do ask. Now it’s very common that candidates don’t ask any questions and go for the simplest, most straightforward solution. This is completely fine, let’s also start with that.
function merger(base, target) {
const result = [];
for(const elem of base) {
for(const tElem of target) {
if(elem === tElem) {
result.push(elem);
}
}
}
return result;
}
Has the exercise ever said the result items must be unique? No. Did they say it should be CPU-efficient on big inputs? No. Now let’s see some follow-ups, they could be in any order, based on the first solution, whatever makes sense.
Follow-up 1
SOT_ARR is expected to be a large array, a large allow-list if you will, while inputs are significantly smaller, but there are many inputs. How would you make the merging process that’s O(m*n) to only O(n)?
function getMerger(base) {
const baseMap = base.reduce((acc, current) => {
acc[current] = true;
return acc;
}, {});
return function (target) {
const result = [];
for (const tElem of target) {
if (baseMap[tElem]) {
result.push(tElem);
}
}
return result;
};
}
Again, there are multiple ways to do it (without libs), and could be questions to be asked on how we should approach, for example in this code I assumed it’s fine to do a one-time setup that’s long (given SOT_ARR is huge), but the actual merging is very fast. I also assumed I could waste memory on creating a helper object that’s at least as big as SOT_ARR.
Follow-up 2
Make the result unique.
This is where it gets interesting because depending on what the previous question and answer were, and also depending on the candidate’s familiarity with the language, this can be either a simple or a fairly complex question. Especially if it’s required to keep the complexity of the solution, and don’t increase memory usage either. Based on the last solution another Object would be the trivial solution, containing the result array as keys, but let’s do it differently now, for the sake of exemplifying there are many ways to solve this exercise.
function getMerger(base) {
const baseMap = base.reduce((acc, current) => {
acc[current] = true;
return acc;
}, {});
return function (target) {
const result = new Set();
for (const tElem of target) {
if (baseMap[tElem]) {
result.add(tElem);
}
}
return Array.from(result);
};
}
const mergerFn = getMerger(SOT_ARR);
console.log(mergerFn(input1));
console.log(mergerFn(input2));
By the way, if the question is speed, Object wins over spread operator and Array.from.
Follow-up 3
Could you improve a solution, if you could assume the inputs are also very large, but there will be a significant number of consecutive calls with the same input, before the input changes?
This is a rather directed question on familiarity with caching/memoization, which is a very common problem in any application, so checking familiarity with it is in my opinion fair game.
function getMerger(base) {
const baseMap = base.reduce((acc, current) => {
acc[current] = true;
return acc;
}, {});
let memoizedResult;
let lastTarget;
return function (target) {
if(target === lastTarget) {
return memoizedResult;
}
lastTarget = target;
const result = new Set();
for (const tElem of target) {
if (baseMap[tElem]) {
result.add(tElem);
}
}
memoizedResult = Array.from(result);
return memoizedResult;
};
}
As always there are many ways to solve it and many trade-offs to decide on. In the shown solution the most major one is memory, we keep the last input in memory potentially way later than they normally would’ve been released. Again a follow-up could be how to solve that if it’s expected to be an issue in the imaginary system.
What do these challenges measure?
familiarity with arrays, array functions, basic JS syntax
familiarity with time complexity
knowledge of scopes and closures (same if they solved it with class or some other way)
familiarity with data structures
knowledge of system design and trade-offs
ability to adapt to changing requirements
ability to ask for clarifications on vague requirements
ability to communicate in general about problems
presentation of coding style and thinking patterns
If you get to know half of the above list about a candidate in about 30 minutes at least on a base level, then in my opinion it’s still worth doing these sorts of exercises.
Comments