Easy coding problems and how to approach them in JavaScript
If you have ever prepared for a technical interview you know that there is a shot that there will be technical questions, sometimes, even interactive coding.
A while ago, I wrote a series on technical interviews from screening, to the take-home assignment, and the whiteboarding exercise. In the years since, I have again been the interviewee, the interviewer, and the expert assessor.
Focusing this series on the step-by-step process that can be unexpected and expected is the key to success, passing is often not alone a guarantee to a role, but something that everyone focuses on, with strong consideration.
Understanding the problem
When we as humans start to read out the problem statement we start to think about the problem from our working memory, we see the pattern, we begin to address it.
At first, we need to think slowly, taking a chance for our minds to turn over the question to think about the considerations. It often helps to list the considerations for the answer as variables to the resolution.
For example, let us start with the following LeetCode interview sample question and examples of the output:
Given an array nums of size n, return the majority element.
The majority element is the element that appears more than ⌊n / 2⌋ times. You may assume that the majority element always exists in the array.
Example 1:
Input: nums = [3,2,3]
Output: 3
Example 2:
Input: nums = [2,2,1,1,1,2,2]
Output: 2
// https://leetcode.com/problems/majority-element/
We can see from the problem and the examples that we are being asked to find the most common number from the parameterized input, in the data type of array, and return the most frequently counted number in the string data type.
As these considerations are acknowledged by the mind, we need to account for them in the code so that we capture our problem-solving, think of these as the todos for what you need to accomplish with the code.
//// nums is an array of numbers [0,1,2,3]
//// the required output is the most frequent number counted
// 1. the array (nums) needs to be evaluated
// 2. the numbers of the array need to be counted
// 3. the counted numbers need to be stored for reference
// 4. after evaluation the numbers need to be extracted from the store
// 5. the most frequent number needs to be identified
// 6. the final value needs to be returned as a string
Working the todos
Now that we have separated the initial requirements from the question and created a list of the first tasks we observe, we can work through the problem, and begin to understand it even more.
As we begin the todos, if another task arises, we should capture it to ensure we return to finalize the problem.
- Our first task is to iterate over the numbers, we know that the most common way to iterate over the numbers is the for loop, for each, and map functions.
//// nums is an array of numbers [0,1,2,3]
//// the required output is the most frequent number counted
nums.forEach((num) => {
})
// 2. the numbers of the array need to be counted
// 3. the counted numbers need to be stored for reference
// 4. after evaluation the numbers need to be extracted from the store
// 5. the most frequent number needs to be identified
// 6. the final value needs to be returned as a string
2/3. Approaching our second task, we need a creative way to count the numbers, a great way to store items in memory without a database, is as an object. As we iterate over the numbers we can use the keys as the identifier, and the values as a counter, similar to a ‘hashmap’ or a ‘store’.
As we write this solution we can even create comments that work as hints to remind us of the pattern we are working through, this can help when moving quickly.
//// nums is an array of numbers [0,1,2,3]
//// the required output is the most frequent number counted
const obj = {} // {"0":1}
nums.forEach((num) => {
if(obj[num]){
obj[num] = obj?.[num] + 1
} else {
obj[num] = 1
}
})
// 4. after evaluation the numbers need to be extracted from the store
// 5. the most frequent number needs to be identified
// 6. the final value needs to be returned as a string
4. Once we have an object with numbers as the keys and counts as the values, we then need to extract those both as values to deduce which number has the most frequent or largest count.
An efficient way of extracting our keys and values from arrays leverages built-in methods to extract the values for both into arrays which can store them back in a more linear accessible format.
//// nums is an array of numbers [0,1,2,3]
//// the required output is the most frequent number counted
const obj = {} // {"0":1}
nums.forEach((num) => {
if(obj[num]){
obj[num] = obj?.[num] + 1
} else {
obj[num] = 1
}
})
const keys = Object.keys(obj)
const values = Object.values(obj)
// 5. the most frequent number needs to be identified
// 6. the final value needs to be returned as a string
5. Now that there are arrays of both the numbers and their counts stored at the same indices in these arrays, we can assess first the values array to find the largest count, then reference its index to find the number with the largest count.
A great way to detect the largest number in an array is to use the Math namespace with the max method, we can again assign this to our object to store the number.
One additional consideration to account for is that we need to pass the values of the array directly to the max method, considering we are passing the array as a reference, we need to use the spread operator to expand the contents as a direct parameter.
Once we know the largest number, we can find the index of that number within the values array and use that index to pick the actual number with the largest count from the keys array.
//// nums is an array of numbers [0,1,2,3]
//// the required output is the most frequent number counted
const obj = {} // {"0":1}
nums.forEach((num) => {
if(obj[num]){
obj[num] = obj?.[num] + 1
} else {
obj[num] = 1
}
})
const keys = Object.keys(obj)
const values = Object.values(obj)
obj['max'] = Math.max(...values) // (0,1,2,3)
obj['index'] = values.indexOf(obj.max)
obj['largest'] = keys[obj.index]
// 6. the final value needs to be returned as a string
6. All of the math considered and produced through running the code, which an important note to remember is that equations can be natively run in JavaScript, we need to return the resulting number.
From the store, we can reference where we set the ‘largest’ key to the object, and return that key to fulfill the required output.
//// nums is an array of numbers [0,1,2,3]
//// the required output is the most frequent number counted
const obj = {} // {"0":1}
nums.forEach((num) => {
if(obj[num]){
obj[num] = obj?.[num] + 1
} else {
obj[num] = 1
}
})
const keys = Object.keys(obj)
const values = Object.values(obj)
obj['max'] = Math.max(...values) // (0,1,2,3)
obj['index'] = values.indexOf(obj.max)
obj['largest'] = keys[obj.index]
return obj.largest
Applying the results
The theory behind this logic is that gathering these todos creates chunks to be addressed easier by the mind, thinking through them references the skill to solve language-focused problems, and explaining the thought process behind it speaks to what considerations come to mind quickly in recall.
All of these processes are based on your approach for hypothetical future work, they reflect how you work daily to implement programs, considerations like organic code, built-in methods, complexity, and efficiency are all measurable outcomes and factors in the interview. Considerations like these are discovered in conversation, where Time and Space complexity are evaluated by processing the application and deciding the patterns' efficiency.
You can find my solution to the ‘Majority Element’ problem on LeetCode.
Often when writing code for projects, we write slightly more complex code, different from these more ad-hoc scenarios. When we need to code to have a systematic theory applied, we look to conditions, operators, and error handling.
A more common example of the function may look something like the one below, where this can become a reusable function for sorting values:
In our daily work, we can think about what writing 100 lines of code that could be 25 means as the system scales. Considerations about languages like built-ins that use the transpiler within the runtime engine which creates repeatable and simpler code can also make an impact.
These above considerations are that which lead to building great software, applications, and functional solutions to real-world problems. As hard as it can be to implement, memorize, and reference these topics through recall, like anything practice makes us all better, and nobody is perfect.