1. Problem visualization and breaking down input/output
Using drawing software or mental mapping to understand input and output before writing any code.
Watch section from 0:32 →Engineering video
25:02 · 16 August 2024
Using drawing software or mental mapping to understand input and output before writing any code.
Watch section from 0:32 →Solving a simpler version first, such as handling a non-nested array before tackling nested structures.
Watch section from 1:21 →Implementing a brute-force solution using a double nested for loop to count element frequencies.
Watch section from 1:39 →Writing a recursive flatten function to convert a multi-level nested array into a single-level array.
Watch section from 4:00 →Storing element occurrences in an object frequency map and iterating over keys to find the maximum frequency.
Watch section from 10:00 →Analyzing the time and space complexity line by line, identifying the initial $O(N^2)$ dominant term from nested loops.
Watch section from 16:00 →Refactoring the counting logic to use a single pass through the array, reducing time complexity to $O(N)$.
Watch section from 19:29 →Communicating intent continuously through pseudo-code and speaking out loud during the coding interview.
Watch section from 22:33 →today, together with Bogdan, we will solve a live coding JavaScript interview question. This is a real question one of our mentees got for a senior front end engineer position. This is a lead code style kind of problem focused on data searches and algorithms. Perfect, so the problem statement is that given an array of integers, we need to write a function that returns an integer that appears most frequently in the array.
Assume that there always be a single one. There's no tie, there's no repeated. And the input can be a nested array of integers. And we got the example. Whenever you are you have one of those, make sure you have some drawing software at hand before the interview, because one of the most useful things to do here is to try to visualize the problem. Perfect, so this is our input as we see, and then there's our output.
And now we try to somehow mentally make a mapping on how to get there, like a pseudo code, but not really, we're just like get a rough idea of what's the input, what's the output. So we see it's a nested array and then we actually go into the array and count all these elements, right? So we have a one that it's actually there twice, right? I see there's a one here and there's a one here, then I have two, that's there one, two, three times.
And then three also repeated itself twice, and that's why I remember the two and the frequency. So we got the two that's repeated itself three times. Exact. And so basically, if we look at the problem, you always want to try to simplify ourselves. Because right now we have an nested array, the first simplification I would do mentally and solve the simpler problem first is to say, well, what how would I solve this problem if I just have a non nested array?
I have one, two, three, and let's two. So let me think about it. How would I solve this? And what we see here is that we could probably have some sort of a double for loop. So I would go through this array in that direction with a for loop. I'll get each individual element and now I'll try to see is it repeated at the right hand. Let's say one is repeated five times. And then maybe I could store that somewhere.
With a double for loop, right? make a second look and go to each one of the elements and add one to my count and store that in an object and say, you know what, for the element one, my accumulator was basically what? Because that's the minimum. I want to stop there for a bit. You would use an accumulator. Yes, I would probably need to store this somewhere because they're asking me for the number that's mostly repeated. So I need a way to store this.
So right now, a double nested for loop would work. That would be my brute force solution for the simplest case. But we see that the array it's not, it's it can be nested. So then probably what would be wise to do is to either recurs somehow, so whenever we find an element that's an array, call the function again or flatten the array. as an intermediate step to our problem. I guess I'm more inclined towards a flattening where you basically eliminate the nested arrays and you have one array that contains all the elements. Sure. The only difference here would make it's the performance.
And also, I would always implement the solution you're most familiar with. So, ideally you do know how to flatten an array, you cannot use lo dash, so you'd have to do this manually. And if you don't, then go for the other solution. That's kind of what I have in mind. Just to clarify a bit, you said a double nested for loop, and I think for me it's it's not easy to visualize this. I do understand the first for loop you were proposing.
Basically, um, having an accumulator and going through all the elements and storing kind of the frequency in the accumulator, what would be the the second part of this? The outer for loop is that I need to do exactly that for all the elements. First, I have one for loop that goes to the one, then it will go to the two. And then I have a second one when I'm at the two, that goes to all the other elements and counts the frequency.
Imagine I have my array here, 1, 2, 3, and 4. What I would need to do to figure out if, actually, let me add a repeated element here. If I want to find out how many times these elements repeat themselves, I would have a first outer loop, I would be the one, and I would go through, I I'm in the outer loop. Let's see, I, to go to zero. is the one. And now inside is I have another for loop that let's say start a J that's equal to Y, and goes through the two, the three, the four until it finds another one an increment. So it is a nested four. Because then I will have to stop and then go to the two do web thing again. Then go to the three and do everything again. Go to the four, do everything again. So the time complexity number of operations here would be n as n being the length of the array by n again. For each number, I need to check all the other numbers and see if there's an application. So that will be my first my first solution, this would work on a non nested array and for a nested array, we might need to flatten the array. So a brute force solution would be number one, I will flatten the array and number two, I will just run this kind of nested for loop. That's a brute force. Given that it's a live coding interview, the first thing I would do is to run and implement the brute force. So I'm going to go over here and start going with it. So, first thing we want to do, let me make this bigger by the way, I think we will add this in the description so people can try the problem themselves before you see me doing it. It's different when you have to do it. And so here we said we're going to flatten. Flatten the array. And second, we want to count the frequency. So I'm going to disable the pilot because it's going to slow the computer fast. Yeah, the question will be how can we flatten an array? So imagine we have an array, has a one, then it has another array inside, has a two, and then let's say we have another element, as it's another one, and we have this. So how do I transform these two? The flatten without using any help. So basically, uh let's try to think a bit about it. We would have an iteration. This is a number, so there's nothing to do. And here we found an array. And whenever we find an element that's an array, we could actually either use the deconstructor or call the have a flatten helper and call the flatten on this one. And then we end up cutting calling the flatten on this one. So we will have some sort of recursion where we basically say, hey, flatten this array, like flatten any array that's my base case and return always a one level array. That's what I say. And then I have this input to the flatten function and I know because of how the
function works. It'll return this two in one. And then I deconstruct that next to one and I end up with a one two one. This one is a bit harder to understand. It's an actually recursive problem. What, I'm going to make this simpler, I don't want to get too recursive and I will use a while loop. And I'll just say, I will just go to the array and whenever we find a an element that it's an array inside it, then, yeah, we could probably solve this with a while loop too.
So, let's try to do it. And so, we said, hey, we want to flatten the array. So, how would we flatten the array? Well, um I'll probably create a new array that is called flat array. And this would be just calling flatten on my and then I'm going to define this flatten function. So, function flatten, it takes an input nested array. And it returns a simple array. So, what does this do? Well, it's going to have some sort of a let's say constant.
I'm going to use some constants here. Maybe we can get rid of them. This would be my new array. that it's an empty one. And then we're going to go with the for loop y y. 200 y that's less than the input. You want to write those things like, you know, if I wake up in the middle of the night, you can write four loops. If not, interviews will be very painful and almost impossible. Perfect. Um I'm going to make this an area of number to make my type pass.
So what's the problem here? Uh yeah, I'm not returning anything that's why it's crying. So let me return like that. Perfect. So we have a new array and we want to say if the current element. gets complicated. What do we want to do? We want to flatten that array and then concatenate to new array. So, I'm going to say uh constant flat is equal to this is recursion. So, I'm flattening. Because this can be another and another one, so I'm going to go inside and all the time flattening it. Yeah. Why is this a type error? Uh no no no no. That's not sign about. So a number or is not assignable if uh but they would assume that this is always. Typescript should know that if I call this array that it's a array that the conditional that doesn't realize that input I. Okay, then if I give this condition, maybe the type checker will be smart enough to understand that if I do this of input of I, I should not have this error. is nested. Okay, I'm doing some very strong type check, but don't worry, most of these interviews don't happen in JavaScript, I do them in TypeScript because it forces you to think about the data structure. Can't you have like a like a conditional type or? Yes, of course, but you don't want to get into those, you might get errors. The easiest way, just type queries and move forward. And now we want to combine. So we could say, um, let me make this a let. And we're going to say, you know what, my new array will be equal to my old new array and my deconstructed flatten array. Okay, now let's see why uh a new array should be a array of number. Okay, and now we're getting kind of oh, the flatten. And here I will say as number. It's kind of weird that doesn't pick it up. Perfect, and now we return the new array. So that would be our flatten This this itself could be a problem. for coding it. That would be our flatten problem. I would get comment, you take this as an exercise and do this a couple of times and write this super fast because they sell so much in here and also like pet figure out the way to do it without the construction with the
for each loop here, because there's so much in this. And it's a very good exercise for people that I interviewing to make you fast. So once you know how to do it, do it 10 20 times once a day until you get good until it's muscle memory almost. We flat array. So let's go ahead and do the rest for our implementation, which was let's go with this double for loop and add those things to this memory store. So this is a trick that's very common, which is we're going to have a what we call a frequency map. And this is just going to be like a um an object and we increment by one whenever we find an occurrence. Right? Whenever we find an occurrence, we will increment by one. So I'm going to go with the follow up let Y equal to in 0. And then we have our stop condition, which is that Y it's less than input. length and Y plus plus. And then we go over and say, if the number, it's not in the frequency map. So a frequency map of my input of Y, it's not there, because if it's already there, we already counted the frequency once. Let me add a tag. So this would be a record and we mentioned we're going to have two numbers basically. Okay, so we use the flat array, perfect. So, if the number is not there anymore, then what do we want to do? We want to add it and then run run a nested for loop to count all Qs. So basically, we want to say that frequency map of flat array of Y, let's see what to one. because I already found it once. And now I want to check if it's still there. So basically, let's go back here, like I added the one once. I said, hey, I already found your once, now let me go to all the other elements and see if I find you again. How many times? So then I can say for let J starting at Y plus 1. because I already added an element. And then I can say J it's less than input. length again. And then I would say J plus plus. And I can say, hey, you know what?
then go in and if the input at J, let's see what he equal to input at I, then increase the frequency map, map at Actually, I I should be using the flat array everywhere, not the input array, frequency map at flat array of I should be equal to itself plus one. So we've found an accurate that should be enough to give us a frequency map. And now the last problem, it's find the most find the most find the max in the frequency map. So basically, we have this, but there's like many of them and we need to find the max frequency.
We can probably do this with a for loop over the keys. So I'm going to say let key max, the max, the key of the maximum be the first element of the flat array, let's say. So let's see, if it was a one, we're saying that's a one, and then we could go, let's say constant keys, so we got to object keys of the frequency map and then we're going to say four Again, another four loop, as you see this is the bedrock of your of your knowledge, very simple things that people usually overlook, that they look to simple, but they're not. They're not simple to do under pressure.
And here I can say if the frequency at the key max it's less than the frequency I find at I, so it's less than the frequency map at keys of I, then make my my new key max. Key max is equal to keys of I. So what is this? It's an any. This should be like a string like a number array or key or string array. I'll assume type string is a number. I it's a string now. So the key is a string. I'll say a string.
Oh my god, so give me a second, as the problem with text sometimes you got to work around it. So frequency map, let me make this a number and what does this? This will return an area of string. So the keys are strings. So, we're So, we know that key so far. Okay. Uh, then we make this get numbers. And that then it will like cry about it. Mm, then I'm going to I'm going to do something very nasty. This is one of the things that you kind of hate about TypeScript, that even if your code makes total sense.
So, I'm going to take the key and just run a parse int on it. Assuming that all the elements in the array are integers, you know, I have numbers. I see the key other max is actually equal to the new key and finally I remember I return a key max, that's my key and the frequency map of that key. Ooh, that was a lot of coding of the key max. Okay. So, as you see, it's a lot of code. I didn't run the tests, let me run them really quick and see what do we get.
Okay, so I have some failing tests, was it 2 and 4, you see, a 1 and a 2. I have to check test cases myself. Probably the edge cases are left rating, should we return the first number in case of a tie? That's something we haven't covered yet. Yes, exactly, so I think that's the main problem right now. Because I would assume, let's see, okay, so return the most frequent number. It it depends on how how the test cases are returned, if they Oh, this is in, MPS, most frequent, deeply nested array, so a two and a four.
It should be one, so let's see what happened here. We have the one, two, it's like three threes. Ah, There's a mistake there, right? I mean, I see I yeah, there's a mistake there in the test case. There's a mistake. We should have another two at least. Yes, and then the tire one I can eliminate it. I go this very fast, but you see, it's a good it's a good thing to to know how to review a test. They want to give you a test in the interview, so Well, anyway, you know, I'll assume that still one that fails should be handle nested arrays and this will be should handle nested.
Should return and return the most frequent number. So this is a two three times. This would actually work right like a two three times. it ten to one. three times. Yeah, because they it's a tie. So, the one was the first element. Okay. And the three. Yeah, it's always when it's a tie. I think that's like the test cases is when not optimized for a non tie case. It's a tie. It's a tie. I don't dance in a tie because there you have oh maybe the first one, right? Oh yeah, that's a tie. And my my implementation it turns a three in these, that's it. So if I go ahead and I just do two, it will probably work because there's no tie anymore. And we can Okay. So, sorry, you turn the two three times. Oh, yeah. But the um the output of this should be the two three times, right? If we look at that problem definition. Yeah, if you change the test case, you better change the expected output, too. Yes. Now, the next thing you want to do here is big o notation can analyze the complexity. And do you want to run the test again to make sure we'll pass and close that up? Because you you change the expected output. Yes, awesome. Good. Um, we will add the link and I will give you the correct test cases to everybody trying this problem out. In terms of big notation, we should actually go line by line and analyze it, but I'm only going to look at the lines that curl. You want to make sure your big o, it's pretty solid. It's one of these easy topics, but people it's also super overlooked. So, this will be kind of for loop. And then if there's an array, we go again. So, this would be. In terms of time complexity, there's no like memory complexity, it's n because we create a new array with the same length. So, time complexity, it's also going to be n as the the number of elements including the nested array. So, think of the array as a nested tree is the number of nodes. So, basically as many elements as you have because we literally have to element to iterate with each one of them and and return it. And in terms of space, this will also be big o often, because we are storing that new array. We could probably optimize this and make it in place so we don't have this space complexity. I'm going to take this with me to here. So, that's an N and an N. Now, let's count the. So, we are creating this frequency map in the worst case scenario, this frequency map imagine there's like no element is repeated.
frequency map size, it would have as many keys as elements in the array. So, this adds another space complexity of M, like memory complexity. And then we have this for loop, which is a nested for loop, and that's when it would be N as the number total number of elements squared in the worst case scenario. O, yeah. Because I do have to I think it's actually going to be divided by two, because we only so we go to the array and only if the element wasn't counted before we counted.
But in the worst case scenario, yeah, this would be like a n square. And and then we have this one, which also it's another for loop. So, it's the same in the worst case scenario. This would be n, because we iterate over the whole keys to extract the maximum. The dominant term, which is the one that's only the one important is this one, n square. And this is when you want to get into performance talk, and probably won't have a time to implement another solution.
But there gets at least one or two solutions you can implement to make this also n. Because in at the end of the day, the um big O complexity of this would be, let me just sum it up, it would be in terms of time complexity, it was ON plus ON and plus ON square. But we only keep terating in your array, then it was the um the frequency map that also Yes. scales, and then it was the double uh four. Yes, yeah.
So, that's kind of it was this double for loop. But there's a way to probably make this. There's two ways to make this problem a lot simpler and probably to go get the complexity down to n. The number one way would be to have not a nested for loop and just whenever we go through the element, if we find it, if it's not in the flat map array, we're just going to add it. So we say, hey, you're already there.
And if not, it means it was already there, we just increment it by one. So I don't need to make a nested for. The nested for is just kind of the first idea that comes to my, okay, I'm going to run the test. And this This is not nice for them. It's a single for loop. So what I'm doing is I have my memory, I go to the array, if I find an element that's not in my frequency map, I add it and I put the frequency one. And if I find the element, in this case one, right?
That was already there. I take the one and I sum it again another one. Instead of counting again and doing the the same the nested loop, yeah. Yes. And now, the complexity it's actually Owen. And um what we have, like going back to our creation, because we reduce that dominant term, we are going to have ON plus ON plus ON, but in big O, the multiplication that's O n, it's actually ON. So that will be the time complexity, complexity.
Because it scales linearly. It doesn't matter what you have. If it's not a a power, if it if it doesn't uh it's not an exponential, then it's ON, right? Yes. And I do not think you can solve this in less time than that. We could have a more elegant solution for sure. There's at least one solution that comes to my mind. What would be your advice to a developer when faced with these kind of problems?
So number one, you want to understand the input and the output and you want to visualize the problem. That's already half of the work. Once you visualize a problem, most problems for the brute force will be a combination of four loops and while loops. You don't even have to do recursion. I recurse for the flatten ray, but you can solve that with a while loop. The problem is most developers, they do know how to write they might know how to write a four loop, but they don't know how to write it fast and under pressure.
And what they don't know is how to combine those things really fast. Okay, I need a four loop here, then it's output. So kind of have that mental muscle. And it's very easy to to uh to get that muscle. It's just going and it's super boring. It's like going to the gym, you take the flatten thing that I did and you try to implement it every three days. Your memory short term memory will will disappear and it's like a muscle.
And you learn how to type it out too, because you need touch typing. Everybody that comes into the mentorship of the senior dev, we start with touch typing. Without touch typing, you are nobody. You don't have to be like at a 100 VPN, but 60 is the minimum. So, if you're interviewing without knowing those things, you are, I mean, it's it's going to be very hard for you to get any kind of success. Once you know those things, it's just a matter of practicing. And the biggest mistake we see people doing every day, is they'd go down the lead code, kind of rabbit hole, they'd go down and do problems that maybe uh Google asks, like dynamic programming, which is like I think Google is the only company, uh one of the few companies that even asks those kind of questions.
And they waste a lot of time in those, but then they don't know how to solve these ones, which are relatively simple if you know four loops. The other thing is you got to know very well big O complexity. The good things it's it's it's usually really easy, you just kind of seeing the the number of operations. Um, and the other thing is be very good at communicating your intent. Right, pseudo code and speak out loud all the time.
I've never went silent during the whole interview. And that's not something I do actively anymore, that's just when you you you give me a problem, the first thing I will do, I'll write pseudo code and I'll start talking about what I'm going to do. What do I do if I get stuck? I mean, suppose I follow all these steps, but I'm still I go blank. The reason why people go blank in coding interviews is because they they didn't visualize the problem or they don't start with the easy part of the problem first.
So one of the tricks that you can use there is identify the edge case. Identify, for example, in this case I did not did it, but what if the y was just in right that one. You know, if MTRay return, you know, and you'll think that that's very simple and it's not going to get near, but you're already writing code. So the coder block, it's broken. And you go back, you visualize the problem, and you try for loops, nested for loops.
You might the only the only picnic light there is, if you see yourself, you get to a point where you're like doing four loops and you identify that you need another nested for loop and another one and you're like I cannot type this, that's because you need the recursion or a while loop. Because you cannot type all those four loops because you don't know how deep you need to go. And if you know those things, I think you'll do fine in just 95% of those coding interviews for the average senior position.
It's a different story if we go into big tech. And that's a very important lesson, you got to pick your battles. interviews preparation. And even after that, I mean, people, you know, getting a job on one of those fangs, there are so many variables involved. Which is why one of the things that we do with the people we coach is to actually build a strategy that's adapted to their goals and also to the time they have.
So that's something you have to ask yourselves, is it worth it for me to learn dynamic programming if I'm not going to interview for Google? For the people watching us, if you are interviewing right now, if you are interested in finding out your interview readiness or your technical gaps, there's a link in the description. Actually, there will be two links in the description, one with a GitHub repo, so you give it a shot yourselves and you do these things yourselves.
And number two, with a free tinkle assessment that we put together to find out, you know, where you're at respect to a senior level and what your tinkle gaps are, which is probably the most important question to answer before you start investing in any kind of training yourself or using a service like the one we offer. With that being said, Bogden, thank you so much, and stay posted because Bogden and I have a full list of interview questions that we're going to be solving like this.