Hi folks, I'm Russia, I'm the senior Dev and today together with, we're going to go to some of the highest and weirdest JavaScript interview questions that even senior devs can't answer. First question, can you explain tail recursion in the context of the event loop? And how is it useful in JavaScript? Okay, so let's talk about tail recursion. So, basically, tail recursion, it's when the last call in a recursive function, it's a call to itself.
To give a quick example, you can imagine maybe inside a framework like React, we have our render function that will take some components and render them. Well, we do know that the last instruction there will probably be another render to to chilling of that component. So that's a recursive call. And probably in between we would have some instructions. In certain programming languages, the compiler would allow you to optimize this.
If you don't optimize this, the memory complexity of the stack memory would be ON. So let's write it down here, so we will have an ON. Where N is the number of recursive calls you are making. The problem is, if N is very big, you will run out of stack memory. That's what we call stack overflow. Let's make this blue, this is our stack. And so every time you call a function, you push a what they call a stack frame, which is a self-contained segment.
of data including the code and all the variables that you need to execute. So it all sits in the stack. Until you basically execute that function and then you pop it out of the stack. So you're going to have a lot of those. When React render it's like a big application. And so what will happen is that if you keep adding like if your recursive chain, it's bigger than the stack, you will run out of stack memory.
And that's what they call stack overflow. Finally, the famous stack overflow. Exactly, and to avoid this, there is an optimization you can make. Where to put it simply, the compiler will, because there's no really diff, there's no really the difference between calling this function, like in between in the code, in a stack frame, except for the arguments. And the way you can think about it, it's basically when you have tail recursion, what the compiler does is that it just saves the reference to a single stack.
And in a way, it will be using that same one with different arguments. But it doesn't store this again and again. So it's just going to use a single one and so all of a sudden you go from ON to O1. But there's a magic condition, which is it can only be done for functions where the last instruction, it's a call to itself with different arguments. It's tail recursion something that we actually use in JavaScript.
Well, it very much depends on who implemented the JavaScript execution engine and it's different in Node.js and it's different in all the different browsers. The good news is, again, it's very much of an edge case. It's not something most applications will use, but as of now, only Safari, only the Safari browser implements it. Because again, JavaScript, those things depend on who implemented the engine that will actually execute JavaScript.
Uh, Bogni, you have a point. This is quite of uh, you know, tail recursion is quite often edge case in the current JavaScript landscape. But we've seen um, more and more concerns about JavaScript apps getting bloated, too much code, too much useless stuff going on. So I think this this kind of edge cases are becoming more and more relevant if what we want is uh, performant apps in 2025 and beyond.
By the way, if you are interviewing right now or if you are simply looking uh to get to the next level as a JavaScript engineer, the best way to start is by understanding what your tinkle gaps are. Check out the free tinkle assessment in the comments, it's going to take you about 10 minutes and it's going to give you a full report on what are the things you need to improve for you to perform better and to get closer to the senior level.
Question number two, how does garbage collection and memory management work in JavaScript? Okay, so garbage collection and memory management. Maybe we can use this drawing over here. We do know we have of the stack. In JavaScript, we basically have the event loop that will look at a stack and keep executing that. But we also have a large section of our memory, like another type of memory, the stack memory, it's what the kind of the CPU, the processing unit of JavaScript will use.
Perfect, so that's our kind of processing unit. There's there's more elements, but you can think of that as the event loop. And of course, this works in combination, following the uh the von Neumann architecture. It works in combination with our memory and I'll call this what they traditionally call the heap, which is a structured memory space. Usually allocated in your uh RAM memory, the random access uh memory that the computer has.
And so, whenever you interpret some piece of code, you'll find data like objects, and you are going to allocate space, right? So let's let's allocate some space. We get a program and we start allocating different memory segments that are being used in our code. So we store an object here, then maybe another object, we put an array here. What usually happens in traditional languages, like C#, is that whenever you allocate a variable, you need to see how much it will occupy.
In JavaScript, to make things easier for us, the developers, it the language kind of tries to guess, more or less, how much space it will allocate. And then whenever you go over that, let's say you have an array and you add more things to that array, then the language will automatically resize the array and relocate. Now, because that happens, it will also try to garbage collect, it will try to clean up the allocated memory when it's not being used anymore.
So imagine in your code you're using this huge array, and now that kind of you're not referencing it anymore, you don't need it anymore. Then the garbage collection algorithm that runs periodically will try to say, hey, this this memory space is now free. So you can place things kind of on top. We never delete from memory, like you can just place things on top. So basically, garbage collection, it's an algorithm that internally all the JavaScript runtimes have.
once in a while and try to free up memory. The problem with JavaScript is that you have closures. And closures mean that if you have a function right and it's attached to event to an event handler or it's being referenced. Right, so that function we can't really delete, we'll keep and usually we store it also in the in the heap. Now, this function encloses some big variables, let's imagine you declare this function and in the same scope, you also have a reference to some big array. Let's imagine you are referencing, you are referencing this, and then you also reference this, and you reference this. Maybe you don't really need them, but you somehow enclose them when you declare your function. And so all of them are somehow needed, right? As dependencies because they they're being enclosed in the lexical scope of this function. So that memory cannot be released when you run the garbage collection algorithm. And what will happen if you have enough of those is that you run out of heap memory. And this is why if you use any modern application, you'll see that even Google Meats in the browser cannot work without 400 megabytes or something along those lines. Right, at the very least, they sometimes had instances when I had a video call and it took over 1 GB of my RAM memory, which is crazy. I mean, computers didn't had 1 GB RAM all the way up until maybe 2010 or 2015 or so. I mean, memory is very expensive and because of closures and JavaScript is not a very memory efficient language. No matter how much they try to optimize garbage collection, you got to make sure that in your code you don't abuse closures. Because closures have a huge memory cost. I think Google Chrome implemented a feature where you can just hover over your tab and it will tell you how much um uh how much megabytes or gigabytes uh that specific tab, that specific application is using through your browser. And uh for me, it was the same. It was pretty surprising to see some apps, some web apps taking up to 1 GB. Do you see when I hover? I just did it because you mentioned it. Do you see when I hover? I don't see it.
uh I I do so I guess it it doesn't show you the overlay the screen sharing but right now this drawing application TL draw it's using 500 megabytes of RAM memory and this other one which is my code sandbox, it's using and I think it's silenced. Uh it's like sleepy, it's using 250. So that's already 700 megabytes for only these two tabs. Keeping in mind the average developer has about 55 tabs open at any point, then we're definitely going to run in uh memory management issues.
Okay, Bogdan, so the point is closures are quite inefficient because you're passing the scope all over. But why do we even use them then? So the reason why we like closures is because it's just easier not to have to pass, imagine all those things. Let's imagine this is A, B, and C. If I do not have closures, in in languages where you don't have closures, you need to pass this as arguments all the time.
And so your function signatures become much longer, and it's just a lot more of a tedious programming, especially for things that are very are constant. So, usually, imagine A, B, and C, they don't really change, they are like global settings, but you need to use them in the function. If you don't include them, you need to pass it as arguments. Of course, you could unify this in a single object, but then it would have three properties.
And so all the it simplifies uh function signatures at the expense of using more memory. Understood, Bogdan. Onto question number three, what is a generator function in JavaScript? Could you write one right now and why would you use one in the first place? Sure, let's try and write one. I have a code sandbox here. So you mentioned generator functions. So basically generator functions are functions that you call and they will give a different result depending on when you call it.
And so, as a developer, I can actually build one, so let's say function alphabet. And so this function, whenever you call it, it will keep giving you different letters. So let's say it will first return, which we call yield here, the A, and then if you call it again, it will yield like B. kind of internal state. And depending when you call it, and then it was yield A C. And finally, let's say we turn on.
And so, whenever you'll call this, it will give this different, it will create sort of an iterator. Um, so basically, we call this letters, let's say. And that's equal to an instance of alphabet. This is a very simplified example. But now I can actually let's console log the letters dot next. And that's how you basically use them. And so, whenever you call them, I'll get a different letter. Until it stops.
So, let's love this. And do you see my console? Yes, I do. Perfect, so as you see, it was first a value A and it gives you this done property which is false, then B, C, D and finally, it's done. It finished. So, when you call a generator function, you get back an iterator, kind of a structure that implements the iterator interface in JavaScript. And you are able to call that all the way until done, it's false, done, it's true.
And you get all those different results. So, it's very good to mimic certain behaviors. To be very honest, I haven't used it so much in production code bases. But it's actually used to create the async await syntax sugar. So, you can use a combination of these functions and promises to actually translate a sync away. And that's why we say that a sync away, it's only syntax sugar. You can find a more detailed explanation of this.
I do nothing I can write it up on my head. But you can think of it as every yield would actually be whenever you write an away. And what it will actually do when you have an away is that this function will end up kind of calling itself. But of course, it will go to the next yield. That's why whenever you finish an away, so, basically what an away would do, it's to call promise.resolve. And when when a promise is resolved, because when you put away you're actually waiting for a specific promise.
When that promise it's resolved, you recursively call this function and it will go to the next yield, which would be the next away. This is a very graph explanation. But that's how a sink away actually works and it's only syntax sugar. It's not language feature in itself, it's literally just translating your async and await into a recursive generator functions that works with promises under the hood to actually give you the asynchronous behavior while you write code that looks synchronous. Is there any other use case you can think of where generators would be besides the async await syntax sugar? Sure, so you can use generator functions to make all kinds of funny things like, for example, an infinite counter. So, normally when you create a counter function in JavaScript, you need to somehow have a counter outside here. We do that, and then we will have a function, and this is called count, and basically it will like increase the counter, so we make counter plus plus, and then we return the count. Whenever I want to increase the counter, I call this function, it will increase counter and then count again. But as you see, this variable, it's enclosed, so it's a lot easier if you have a generator. Okay, you can call we can call this create the counter. And basically what we want to do with a generator is that we initiate that inside the function, which is a lot more memory efficient, looking back to our discussion on garbage collection. So, we have our counter and with a generator, we just want to keep returning while true. So, because they don't go on it will only be invoked if I if I call it again, right? So, this is not an infinite loop. And then I can say increase the counter and then return it, return the counter. And so, let's create it. Like see, constant counter, let's see, it's equal to my create counter. We could even have an initial value if we wanted, but let's leave it as it is, and then I can just like console.log our counter. So, this is our counter. next. So, every time I call the next method, I get a new count. Let's call it a couple of times. Ah. Okay, so this would be a yield, yield, perfect, and now it's going to work. And I can do this until a thousand. So, I managed to somehow have an infinite counter. I mean, this is a very simple example, but you can use it for all
kinds of functions with sort of an internal state or you can have an internal state and modify that and then every time you call it, it gives you the next value. Well, enough about generators for now. Question number four and this question is about array.sort, the array method, what would you say is the time complexity of this array method, array.sort? Well, that's a tough one, so array.sort. And this will basically get a sorting function and return this sorted array. If I recall well, JavaScript uses um what they call team sort, which is I think it's combination between quick quick sort and insert sort. What I do know for sure is that the time complexity of this, the average one, so the average one would be n, which is great. In the worst case scenario, no, the the average is n log n. I think the best case scenario it's n, which is amazing for us. Best case, it's n, the average is n log n. And the worst case, it's worst case. It's n log n. So that's pretty much it's a it's a variation of sort, that's pretty complex to implement and it's a combination I think of insert, insert, sort and quick sort. But yeah, there's there's like thousands of sorting algorithms out there. Um I think most JavaScript runtimes implement team sort. Definitely V8, which is the Chrome engine for JavaScript, implements team sort. Is there any other sorting algorithms do you know about, Bogdan, and uh which would be your, you know, go to quick solution if you had to use one? So if I had to implement one, I would probably go I'll probably start with the simplest ones, which is selection sort. In in selection sort, it's pretty bad, uh performance wise, but in selection sort you would can iterate to the array. And you look at the right hand array and pick the minimum, you compare the minimum with your current element. And if the minimum is lower than your current element, you swap. So it's super easy, right? It's just fine.
the minimum and you keep going that, but it it has an n square, right? It's n by n. Uh and then it's like uh this bubble sort where you go to the array and you you just swap elements with the next one. So you compare uh it's level B. So you compare the current element with the one in the next position and if it's smaller you swap. You need to do a couple of runs to sort the whole array. And so um it's also very like it's not very memory efficient. There's merge sort, which is recursive, there's insert sort. So there's just so many of them. If someone would tell me implement a sorting algorithm, I'll probably start with the simplest one that I remember. And in my case, it's definitely selection sort and after that one, there's the bubble sort, which are very easy. And after that, I will look at merge sort because I know I know that solution is recursive and I know it kind of splits the array and there's a base case and then you put it together. So I probably could work my way through it. On to question number five and final question, have you heard about object proxies in JavaScript? Can you give a real life example where they are being used? So as the name says, it's it's a proxy and proxies are always things that sit in the middle. So that's our object with a proxy instead of giving the the actual object to the user. You can think of it as we wrap it in a proxy. So we basically are wrapping it in this fancy proxy and whenever the user will call into this object, we have the opportunity to add get and setter functions. So we could add some logic in here, let's say imagine. This is our logic uh to do things before we call into that object. So when I make a get or a set, I go to that blue box before I actually get in return. Right, the value, um and so, yes, exactly. Now it's actually work perfect and so it's the same, it would go into that, but go through a proxy. Like this is a proxy logic. So, I think one of the best use cases it's to implement something like state in React, like reactive state, because you could actually create a state object and this is our value. Let's imagine this
is a counter. And so, whenever people want to update that counter, and and we actually do that with the set state. So you you create your state object, and then you update that. Whenever they update, we could actually make the update, but then, you know, in a blue logic, make sure we also trigger a re-render. Now, that's not exactly how React does it. It's a lot more complicated under the hood. But if you tell me, Bogdan, implement the reactive state in two minutes, I'll probably use a proxy object. And I will create a create state handler, and that create state will take whatever initial state you give it, and give you back some setter. But in reality, when you call that setter, I will in the background also run all these functions. So basically, when you create a proxy, just to show the the code, you would do something like new proxy and then you're passing your state, and then you'll have your handlers. So, basically, whenever you want to create a state object, I'll actually run the proxy and pass in my own handlers. And basically the handlers are basically functions that look like, for example, set, where I think you get like the the key and then the actual value, and then the next value, something along those lines. I make I will have to check the reference, but this would allow me to in my set function and I define. Somewhere before after I do the update, so I I just say, you know, the the state or the key you gave me, it's equal to the new value. This looks exactly like a normal object update, but here, I can actually say rerender. And I would be able to create reactive components or state with basically native JavaScript APIs by using proxies and wrapping those state objects. Well, there is a bonus question though, we are not done yet. Finally, I wanted to ask you, is there any upcoming JavaScript feature you are excited about? I mean there's a lot of low key features that are cool, but not super interesting, but I would say the best one that I'm looking forward to it's signals. And basically signals allow you to natively create what I just mentioned before, which is reactive state or reactive variables in plain vanilla JS. So you don't need react or
you can literally subscribe in vanilla JavaScript to certain values and then they would automatically react or update or re-execute certain code when the value updates. So it's basically implementing the observer server pattern natively in JavaScript. And I think it comes because it will really eliminate a lot of the overhead in terms of frameworks, and it will give a lot of power to Vanilla JS. This is still in early consideration, so maybe we'll have to wait another year or maybe two years for it.
But I think um frameworks will have to be redesigned because all of a sudden the reason why you needed a framework like React or preact or whatever was because you wanted to have reactivity. But if you have that in Vanilla JS, then you all of a sudden can just do a lot of things with Vanilla JS. Especially now that we also have components and imports. So I love the fact that a lot of the features that were battle-tested in modern frameworks now get added natively to JavaScript.
Amazing, Bogdan. I mean, this seems like a very innocent question. But it's very important in tical interviews that you have something like this prepared, right? Because um it's a very crowded market out there. Everybody knows React, everybody claims to know JavaScript. And this is a very good way to not just tell, but show that you actually care about your craft, that you actually care about where the JavaScript ecosystem is moving.
And you do your best within your daily job, of course, to stay up to date. Okay, folks, Bogdan, as always, thank you so much for the amazing performance and for everybody watching, by the way. If you are interviewing right now, or if you're simply looking uh to get to the next level as a JavaScript engineer, the best way to start is by understanding what your ticle gaps are. Check out the free tical assessment in the comments. It's going to take you about 10 minutes and it's going to give you a full report.
On what are the things you need to improve for you to perform better and to get closer to the senior level. With that being said, thank you so much, folks, and we will see you in the next one.