Your cart is currently empty!
An Experiment to Crack the Coding Interview
Having only graduated Flatiron Software Engineering School this past Monday, this blog post is somewhat speculative. It’s about a hypothesis, one that I will get to test very quickly thanks to a parting gift from Flatiron. A token to SkilledInc, where we get tested on our technical chops.
One of the things that it seems many graduates have regrets about is that they started on Data Structures and Algorithms too late to actually be “job ready” right after graduation. Sure, the bootcamp helps you build a killer portfolio, become a competent “builder” of code… but there isn’t much concern as to how well you will do on a coding interview, especially if it focuses on DS and Algos. Part of the reason I began this blog is that the power of analogy is a long term strategy to help with recall on things that I might study months back. However, with only a few days to my mock technical review, the prospect of grinding out analogies and working through code challenges on AlgoExpert, Educative’s Grokking Course, LeetCode, and all that I have on Udemy made me realize there didn’t seem to be a very effective short term strategy for “cracking the coding interview”. While thinking about this, I began to wonder if there might be a better way to prep for my mock technical review only a few days out. This blog post is a hypothesis that there is. Strangely, I’m very excited about the mock review that I know I’m unprepared for, because not only will I have the opportunity to be “graded” on my code-sense and know what to improve on in a traditional manner, but I have the opportunity to try a different approach to studying and see if it works. For, if I do well, then I’ll know I can double down on my hypothesis and do more with less. If the approach leads me to fully bomb the mock technical interview, that’s fine too because I suspect I might’ve bombed anyways (unless I got lucky with a familiar coding question).
SOME THOUGHTS about WHY experiment instead of just “doing the best I can with what’s tried and true.” A mock technical interview on Skilled runs a couple hundred dollars. If I were paying for it myself (as opposed to with this token that I didn’t expect to receive), I don’t think I’d have the courage to spend that money on an experiment. LIKEWISE, if this were a real job interview, I also doubt that I’d have the gall to do something experimental if a job were possibly on the line.
SOME THOUGHTS ABOUT WHY I think the “experiment” might work, and then I’ll jump in to what it is. If you look at most coding interview literature, you’ll find that people get jobs even if they fail the coding challenge… many instances where they get hired are if the pseudocode and actual solution were “close enough”, if the interviewee asked great questions, and came up with a workable solution. We often hear that The Coding Interview bears no relation to what you will be doing on the job, and people lament this. On the flip side, The Coding Interview is something of an equalizer in that its focus is on your ability to articulate how you think as a coder, and also give you an opportunity to “implement”. The Data Structure/Algorithm interview gives the interviewer a chance to see how you think under pressure and how you break problems down.
With that in mind “the experiment” is about focusing on things that I suspect will provide the biggest bang for the least amount of effort. NOTE: this isn’t about being “lazy”, but finding out whether or not this minimal time-wise (yet still intense effort) will yield. If it does, then I suspect I might get a 10x return for all future efforts. Of course, if it’s a disaster, I’ll know in a few days and report back.
“THE EXPERIMENT/APPROACH”
Identify a universal approach for tackling coding problems that has the potential to be 10x better than the vague advice of “break the problem down”. AND, figure out how to effectively translate that into “real” code.
Defining “universal”. Something I can do EVERY time.
Defining “10x better”, something that is concrete and repetitive (reliable to every situation).
Defining “effectively translate into real code”, recognizing that brute force and built-in solutions may not be the the optimal solution, but at least a working one.
What I came up with is a business/manufacturing analogy inspired by Eliyahu Goldratt’s THE GOAL, that allows you to iteratively work through an abstract problem as if every aspect of it were concrete:
- Look at the “data input” for the problem and “data output” in the context of manufacturing. The input can be considered the “raw materials”, the data output something akin to “the deliverables for the client”. In a coding interview, “the client” is the person interviewing you.
- Determine the limits and constraints of both input and output, and even the possible solution… this would be akin to “finding the client’s pain” and filling out a “work order”. At this stage, determine what inputs are viable, what aren’t, and what to do with edge cases. Regarding output find out if the “requested output” is actually what is required or if it ought be rendered in an a way that’s extendable. In the analogy, it’d be like asking: “I know we’re building cars here, but should the frame of the car be built in such a way that a hybrid/battery could be swapped out for the combustion engine.” In the coding interview it’s about finding out if the solution you embark on is extendable should they try to throw any curve balls at you. In other words: “I know the requested output is to render all the values as above, but should I craft my solution in such a way that it could handle different inputs?” Lastly, the possible solution is about finding out Big O constraints. In a business analogy, it could be likened almost directly to “THE COST OF LABOR”. Writing nested “loops” is like delivering late and over budget.
- Once you have a sense of what the final output is you gotta get into “foreman” or “floor manager” mode. Consider every piece of your coding solution to be akin to creating work stations, buying equipment, and outsourcing labor… As for breaking this down into something workable: list the most final values/returns, AND most self evident values/returns you’ll need to hit your goal. You won’t be able to get everything, there WILL be surprises, but being able to define things you’re confident you will need will help you more quickly organize your thoughts and fill in the blanks. Treat “Helper Functions” as outsourcing, or consultants. In other words, if your primary function is “The Main Factory” where you manufacture a solution, then a helper function is something that doesn’t make sense for your workers/your factory/(the primary code to do). If you’re dealing with something that has nodes, or some specialized class object, then you will likely want to treat that as a separate “manufacturing plant”, “consultant” or whatever. In the context of coding questions where you have some pre-existing classes to work with (nodes for example), it seems easiest to think of them as “Vendors”… Treat built-in methods, sort, filter, map, reduce etc as “equipment” you may want to buy. Sure, you may not be allowed to write str.reverse( ) on a string reversing code challenge, but knowing what “equipment” is available, especially if it’s only some portion of a given problem will enable you to work faster to a working “brute force” solution at the very least. Lastly, consider temp containers, if statements that escape loops loop, and other final “breaking values” that help you get to a more final return to be your Quality Control team. It’s easy to take this part of the analogy for granted because it feels as if it’s just part of the process… but once you start looking at code not in the context of loops or whatever else, but simply as “manufacturing processes”, the idea of quality control becomes easier to keep track of. To recap and summarize: list what you KNOW you will need with 99% certainty. If you know you’re displaying keys and values, and you’re not starting out with data in that form, then know you will need a container object to hold the final result. If there’s sorting involved, know that you will likely need an array to hold the return.
- The above step is essentially the “DO OVER” and “REPEAT” of this process, but it differs from typical “break it down pseudocode” in that most people write pseudocode linearly. The purpose of this process is to think of yourself in a board room assessing a problem and assembling your team. YES, you will likely write your final code linearly, but thinking about everything with a “bird’s eye view” will help prevent you from going on any wild goose chases that almost inevitably occur when you are considering only what’s in front of you.
- Once you’ve identified enough of the major steps to get you from beginning to end, it’s time to start coding…
- USE CONSOLE.LOG ALL THE TIME! Consider each console.log to be a stand-in for you, watching the process unfold.
- Parting thought about the analogy… code typically can be organized into SETUP, THE ASSEMBLY LINE, OUTSOURCING (if applicable), and FINISHING.
PREPPING FOR THE MOCK INTERVIEW
As I worked to embrace the above analogy, I dove into AlgoExpert to study all the Easy, Medium, and Hard solutions, to see if there were certain built-ins that I should pay more attention to than others. The thinking went as follows: I might not be able to identify the correct algo pattern for a problem, but if I were able to more accurately and quickly select some built-in option that’s generally successful, I might then be able to work more quickly towards a brute force solution to get the job done. The thinking also went that knowing the most common built-ins, in a less vague way, would allow me to wield the most powerful ones more effectively.
What I did was this: I made a list of any built-ins that I came across and tallied it as “1” if it appeared in a solution. If there were “10 while loops” in a single solution, I still counted it once. After spending a few tedious hours to do this I came up with the final results for Easies, Mediums, and Hards. I then attempted a trimmed down version of the above process to write code that would allow me to sort my mess by values. The code isn’t pretty but here it is… NOTE: some of the ways I tallied certain functions is inconsistent, but easy enough to remedy for the final result.
const stuffToStudyHard = {
instantiateNewClass: 15,
sortAB: 1,
destructuredArray: 0,
objectKeys: 1,
sortVanilla: 0,
sortABcontrolFlow: 0,
objectValues: 0,
inKeyword: 7,
nextKeyword: 10,
someKeyword: 3,
filter: 2,
map: 9,
reduce: 4,
lodash: 5,
slice: 5,
concat: 2,
loopForExtended: 18,
leftAndRightIdx: 13,
loopForOf: 9,
continueBreak: 5,
loopWhile: 17,
makeUseOfInfinity: 8,
arrayFill: 4,
builtInMathABS: 1,
builtInMathMax: 13,
builtInMathMin: 4,
builtInMathFloor: 6,
constructorMethod: 5,
customClassMethods: 5,
ifElseTypicalLoopOftenRecursive: 9,
customSwap: 6,
ternaryExpression: 4,
charCodeAt: 0,
fromCharCode: 0,
indexOf: 0,
splitLengthJoinShiftPopUnshiftEtc: 22
}
const stuffToStudyMedium = {
sortAB: 4,
destructuredArray: 1,
arrayKeys: 1,
sortVanilla: 1,
sortABcontrolFlow: 1,
objectValues: 1,
inKeyword: 3,
filter: 0,
map: 2,
slice: 4,
concat: 2,
loopForExtended: 20,
leftAndRightIdx: 16,
loopForOf: 8,
loopWhile: 15,
arrayFill: 2,
builtInMathMax: 3,
builtInMathMin: 2,
builtInMathFloor: 3,
constructorMethod: 14,
customClassMethods: 8,
ifElseTypicalLoopOftenRecursive: 12,
customSwap: 7,
ternaryExpression: 3,
splitLengthJoinShiftPopEtc: 19
}
const stuffToStudyEasy = {
sort: 1,
filter: 0,
loopForExtended: 7,
loopForOf: 8,
loopWhile: 10,
builtInMathABS: 2,
builtInMathFloor: 2,
constructorMethod: 4,
customClassMethods: 1,
ifElseNoTypicalLoopOftenRecursive: 6,
customSwap: 5,
ternaryExpression: 4,
charCodeAt: 1,
fromCharCode: 1,
indexOf: 1,
splitLengthJoinEtc: 10
}
function getKeysAndCounts(studyObjects){
// let test = {name: "Adam", count: 5}
// let test2 = {name: "Adam", count: 5}
// console.log(test.count + test2.count)
// test = test.count + test2.count
// console.log(test)
arrayOfObjects = []
//TAKE THE OBJECTS FROM STUDY OBJECT AND CONVERT EACH KEY VALUE PAIR INTO ITS OWN OBJECT
for (let line of studyObjects) {
//console.log("each line", line)
let tempObjKV = (Object.entries(line))
for (let [k,v] of tempObjKV) {
//console.log("k: ", k, "v: ", v )
let idX = 0
if (arrayOfObjects.find( function({name}, index) {idX = index; return name === k}) ) {
// console.log("ALREADY", k, "at ", idX)
// console.log("previous counter is", arrayOfObjects[idX].count)
arrayOfObjects[idX].count = arrayOfObjects[idX].count + v
// console.log("new counter is", arrayOfObjects[idX].count)
} else {
arrayOfObjects.push({name: k, count: v})
// if (innerResult[k] != null) {
// innerResult[k].total+=v
// } else {
// innerResult[k] = {total: v}
// }
}
}
// console.log(arrayOfObjects.length)
// console.log(arrayOfObjects)
}
//console.log(innerResult)
sortedArrayOfObjects = arrayOfObjects.sort(function(a, b) {return b.count - a.count})
for (let line of sortedArrayOfObjects) {
console.log(line.name + ": " + line.count)
}
}
getKeysAndCounts([stuffToStudyEasy, stuffToStudyMedium, stuffToStudyHard])
The final output from my code was this:
loopForExtended: 45
loopWhile: 42
leftAndRightIdx: 29
ifElseTypicalLoopOftenRecursive: 27
loopForOf: 25
constructorMethod: 23
splitLengthJoinShiftPopUnshiftEtc: 22
splitLengthJoinShiftPopEtc: 19
customSwap: 18
builtInMathMax: 16
instantiateNewClass: 15
customClassMethods: 14
builtInMathFloor: 11
ternaryExpression: 11
map: 11
splitLengthJoinEtc: 10
inKeyword: 10
nextKeyword: 10
slice: 9
makeUseOfInfinity: 8
arrayFill: 6
builtInMathMin: 6
sortAB: 5
lodash: 5
continueBreak: 5
concat: 4
reduce: 4
builtInMathABS: 3
someKeyword: 3
filter: 2
sort: 1
charCodeAt: 1
fromCharCode: 1
indexOf: 1
destructuredArray: 1
arrayKeys: 1
sortVanilla: 1
sortABcontrolFlow: 1
objectValues: 1
objectKeys: 1
My doctored/interpreted result was this (manually altered results for consumption)… HUGE HEADS UP!!!! the examples
are not actual syntax, just how I was tallying. I’ve supplied LINKS to all the MDN documentation for everything covered, and in some cases “sibling commands”. Once I used Stackoverflow, but otherwise all from MDN.
loopForExtended
: 45
this is the typical for (let i = 0; i > array.length - 1; i++)
type of loop.
…see this for loop on MDN.
loopWhile: 42
this is pretty self explanatory, used with boolean conditions.
…see this while loop on MDN.
leftAndRightIdx: 29
though denoted as “left and right” these were really any index that might be considered “positional”. Not only left and right, but Heads and Tails. Beginnings and Ends. Associated ideas/use could be likened to “pointers” though typically incarnate in the context of Nodes
.
ifElseTypicalLoopOftenRecursive: 27
Of all the reveals, this was the most interesting. Basically, there is no “loop” as a “loop” is frequently thought of. What would happen is there would be a series of if/else statements that would recursively (i hope i’m using the term correctly) call the same function. The “loop” would break
if an early if
condition were met. Of course there were many typical if/else statements, but seeing it used like a “while loop” felt enlightening and empowering.
… if…else on MDN…
loopForOf: 25
following syntax of for (let obj of arrayOfObjects)
.
…see the for…of on MDN.inKeyword
: 10
…
I’m including reference to the “in” keyword here. Though I don’t recall seeing this syntax for (let key in Object)
, it’s worth mentioning since you can use it as a special case of a for loop. The way I DID see the in
keyword used was typically in If
Statements.
…see the for…in on MDN.
…see the IN operator on MDN.
constructorMethod: 23
instantiateNewClass: 15
customClassMethods: 14
nextKeyword: 10
The three above methods regard the creation of classes or use of classes. Mostly for use with trees, nodes, etc. If you’re using these you’ll almost definitely be using a positional index. Also likely using some custom swap
method. The thing worth pointing out here, is that instantiating new objects from a class during an iteration (from inside of a loop) was the most common use of “new”. The next
keyword was frequently in use when a constructor method was employed, but I suspect that’s because so many of these dealt with nodes.
…defining classes on MDN…
…constructor ARGS, on MDN…
… NEXT keyword…. <– !!WARNING!! this is slightly more complex in that it does not work the way you might expect it!!
…in the context of NEXT…. function* , generator, and yield.
BREAD AND BUTTER METHODS: split, length, join, shift, unshift, pop, push
appeared so frequently that you ought be familiar with them all. Fortunately there aren’t many optional arguments to be familiar with so knowing what they do should be enough. Slice
is a bit more involved, and seems to warrant some special attention for its syntax.
ALSO …concat: 4
Reading it in code was not particularly intuitive and therefore something to be aware of,.
RE: THE ABOVE, FROM MDN:
… concat …
… join …
… pop and push …
… slice… <– this one can be a little more involved.
… shift and unshift …
… split …customSwap: 18
Fairly frequently there would be custom swap methods where a swap function
would take in two values (I can’t recall if more) and replace one with the other. It appeared 18 times in about 70 something problems (which is almost 25% of the time). It’s something I intend to memorize for convenience.
builtInMathMax: 16
builtInMathFloor: 11
builtInMathMin: 6
builtInMathABS: 3
The built in Math.max method was most common for returning the larger of two values and frequently used in tandem with iteration. Math’s Floor method not super frequent, but common for certain types of problems. It warrants being aware of how it works. Math min is the opposite of max so the effort to learn it goes without saying. Math.abs (for absolute values) is useful for dealing with number problems that have negatives, but was very uncommon.
… see ALL Math features on MDN …
in particular, MAX, MIN, FLOOR, ABS
ternaryExpression: 11
I almost feel that you don’t really need to know this as you can always write it out with extended syntax. However, it appeared enough, especially in those If/Else “Recursive” functions to manage control flow that you should know the syntax. It seems reasonable to expect an interviewer might ask “can you refactor this into a ternary expression”
… ternary expression on MDN…
map: 11
much less commonly used than I would’ve expected, but if you’re dealing with Big O, perhaps it’s to be expected that you will be required to manually write stuff out.
… map as frequently used, from MDN …
… not to be confused with the Global Map object …
makeUseOfInfinity: 8
It wasn’t a ton, but values were set to Infinity
enough that I suspect I’d like to be as familiar with it as I am with undefined
.
… Infinity on MDN …
arrayFill: 6
Not super common, but useful when you need to pre-populate an array. I had the impression you could solve without it, but it wasn’t a one off.
… fill on MDN.
sortAB: 5
sort: 2
sortABcontrolFlow: 1
So, you are probably familiar with .sort( )
, but you should also be familiar with the extended syntax that uses sort(function(a, b) {return a - b})
as it can be useful for inelegant solutions if sorting isn’t necessarily the key point of the exercise. The sort control flow situation uses boolean expressions to determine returns of 1, -1, or 0
. This was less common, but useful if you need to do something more involved and I feel (despite its rarity) something I intend to memorize.
… the many incarnations of sort on MDN…
lodash (or is it an underscore??): 5
Not for certain if I’m using the term lodash correctly as I know it’s something you can import. That said: the use of the underscore appeared several times in code. I suspect it was just to grab the previous unstored yet returned value, but something I intend to glance at prior to the mock review. UPDATE to previous text, this explanation on stackoverflow seemed satisfactory.
continueBreak: 5
**
break, continue were frequently used with next and they actually appeared more than 5 times though I didn’t start tallying till after I was almost done. I mention them at all because while I think we mostly know how to use them, or how they’re intended to be used, there have been times where it’s caused an unexpected result (and worthy of a glance).
…see break on MDN.
…see continue on MDN.
…BONUS… see switch statement on MDN.
reduce: 4
This can be used to help you sum values. It’s a powerful feature, but because it’s hard to immediately guess the return, and you can add optional arguments, it’s something to know. In one sense, I suspect the reason it was rarely used in the solutions may have to do with Big O performance and also that using it would defeat the purpose of using a more standard loop… but for a brute force or even fancy solution, my impression is that it is a useful tool to have.
… reduce on MDN and also reduceRight …
someKeyword: 3
rare, but useful. I will probably look into its alternatives when looking.
… some on MDN …
… every on MDN …
filter: 2
Was sorta shocked that this didn’t get used that much. I doubt I miscounted, but perhaps it’s something that is frowned upon in coding interviews since so much of what we are required to do is “filter” manually. Seems useful for writing code quick and dirty and worrying about refactoring later.
… filter, and related on MDN…
… find …
… findIndex …
… indexOf … indexOf: 1
charCodeAt: 1
… see charCodeAt on MDN…
… see fromCharCode also…
fromCharCode: 1
… all about spread syntax …
destructuredArray: 1
array.keys and also Object.keys(someObject) on MDN
arrayKeys: 1 objectKeys: 1
objectValues: 1
and ObjectEntries
objectValues, objectEntries on MDN.
The above were mostly miscellaneous and used for specialty problems. My impression is it’s worth knowing what they are so you can use them when you need them. NOTE: Object.keys(object)
is the syntax… though I mentioned at the top of this section that what’s listed is not actual syntax, coming this far and the way they are presented may create an illusion of something searchable.
EXTRA, EXTRA READ ALL ABOUT IT!!!
statements and declarations by category on MDN.
OTHER EXTRAS: BIND, CALL, AND APPLY
… apply, bind, call …
FINAL THOUGHTS
Armed with a flexible process to focus on specifics, and a belt full of the most commonly used syntactical tools (at least in the context of AlgoExpert’s Easy Medium and Hard solutions), I am uncertain of how I will ultimately perform in two days from now. However, I now look forward to the test with excitement instead of dread, for a chance to prove (or disprove) a budding theory.