# Master JavaScript Execution Context — Hoisting, Scope & Closures Made Clear

https://www.youtube.com/watch?v=Efqj3FV2vjE

[00:00] Hi everyone, hope you all are doing well.
[00:01] Today's topic is JavaScript's execution context.
[00:04] Many of you have repeatedly requested me to make a video on this topic in the comment section of my channel.
[00:09] I hadn't responded for quite some time and there was a reason behind that.
[00:13] I [music] wanted to make sure we first covered the fundamental concepts of JavaScript before diving deep into execution context.
[00:19] There's another reason too.
[00:21] Once you finish watching today's video, you will realize that many of the topics we discussed [music] earlier like scope, hasting, and closures will start making much more sense and feel newly discovered from [music] a deeper perspective.
[00:34] We already have a preliminary idea about these topics especially if you have been following my videos on this channel.
[00:41] So today through the lens of context we are going to rediscover all those concepts by [music] understanding execution context in a completely new way and by the end you will finally [music] understand how everything connects together how two hands make four.
[00:58] So without wasting any more time let's get started.
[01:05] Before diving into JavaScript context, let's take a step back and see how JavaScript actually runs, whether in a browser or in a terminal.
[01:13] You might be wondering why I'm mentioning the terminal.
[01:15] Well, that's because JavaScript now also runs outside the browser through NodeJS.
[01:19] So, let's first explore how that happens behind the scenes.
[01:23] So, the code you write in JavaScript when it reaches the browser, or in the case of NodeJS the server, it's ultimately executed by the computer.
[01:32] But here's the thing, computers don't understand JavaScript.
[01:34] They only understand binary or machine language.
[01:39] So somehow the browser has to translate our JavaScript code into machine language so that the computer can actually run it.
[01:46] That means there must be some kind of mechanism inside the browser or in the NodeJS runtime that performs this translation from our programming language to machine language.
[01:57] Right.
[01:58] Exactly.
[01:59] That mechanism inside the browser is called the JavaScript engine.
[02:02] In Google Chrome this engine is known as the V8 engine.
[02:05] Other.
[02:07] Browsers use their own engines.
[02:10] Firefox uses Spider Monkey.
[02:12] Internet Explorer uses Chakra and Safari uses JavaScript core.
[02:15] No matter which engine it is, every browser has one and they all perform the same code task converting our JavaScript code into machine language.
[02:26] Different browsers may implement this process in their own way, but all of them follow the same standard known as the ECMAScript specification.
[02:33] ECMAScript is basically the official standard that defines how JavaScript should work like ES5, ES6 and so on.
[02:41] Since each browser's engine implements the standard slightly differently, sometimes the same piece of code may produce slightly different outputs in different browsers.
[02:49] Anyway, once our code reaches the browser, it's handed over to the browser's JavaScript engine.
[02:55] The engine then converts that code into machine language which the computer can finally understand.
[03:02] And that's how we get to see the output displayed on our screen.
[03:03] So that's the overall top level view of how JavaScript code runs.
[03:06] Now as
[03:09] Programmers, we don't really need to understand what happens at the hardware level because our focus isn't building hardware.
[03:15] We are application developers.
[03:18] What truly matters for us as JavaScript developers is understanding what happens inside the engine when it runs our code.
[03:26] In other words, we need a clear picture of how the JavaScript engine actually compiles and executes our code.
[03:30] That's the foundation of everything.
[03:32] So, let's start right there.
[03:35] But before we do, there's one important thing I should mention.
[03:39] I have been talking mostly about browsers.
[03:40] So, you might be wondering what happens in the case of NodeJS.
[03:44] Well, NodeJS actually uses the same V8 engine that powers Google Chrome.
[03:49] Ryan Dal, the creator of NodeJS, is a truly brilliant programmer.
[03:51] What it did was take Chrome's V8 engine which was already built, tested and extremely fast and use it outside the browser.
[04:03] V8 is one of the most powerful and highly optimized compilers in the existence.
[04:07] It converts JavaScript into machine language with incredible speed and.
[04:11] Efficiency.
[04:14] That's why Google Chrome performs so well because of the V8 engine.
[04:16] Ryan realized that V8 was already the best engine out there.
[04:21] So he thought if he could extract it and use it outside the browser, he wouldn't need to build a new engine from scratch.
[04:28] The compilation part would already be handled by V8.
[04:30] Since V8 was written in C++, Ryan integrated it directly into his own C++ program.
[04:39] In other words, his C++ code and the V8 engine were combined.
[04:42] And that's how the NodeJS runtime was created.
[04:44] Now let's take a look at what actually happens inside a JavaScript engine when it compiles JavaScript code.
[04:50] To understand that we need to start with a bit of history.
[04:55] When JavaScript was first introduced, it was an interpreted language.
[04:57] Meaning there was no compilation process.
[05:00] There wasn't a compiler to convert JavaScript into efficient machine level code.
[05:04] And here we come across two important terms: interpretation and compilation.
[05:10] At
[05:13] First, interpretation and compilation might sound like the same thing because both convert our code into machine code, right?
[05:19] That's true in essence, but the way they work is completely different.
[05:24] Let's take a closer look at the difference between interpretation and compilation.
[05:27] Here we have some JavaScript code written by us.
[05:32] This code is readable to humans, but the computer doesn't understand it.
[05:34] So, what does a computer understand?
[05:36] It only understands binary zeros and ones.
[05:39] That's machine language.
[05:42] And while the computer understands it perfectly, we humans can't read it.
[05:46] So our job is to take the human readable JavaScript code and convert it into machine code.
[05:51] So the computer can understand what we are asking it to do.
[05:55] There are three main ways to perform this conversion.
[05:59] Interpretation, compilation, and the number three is a hybrid approach.
[06:02] A mixture of both.
[06:04] Back in the '90s, JavaScript used to be a purely interpreted language.
[06:09] But in recent times, modern JavaScript engines use a.
[06:14] Combination of both interpretation and compilation to translate code into machine language.
[06:21] The language itself hasn't changed.
[06:23] It's still that JavaScript, but the way it's implemented has evolved dramatically.
[06:29] Now, let's see how an interpreter actually works.
[06:31] How JavaScript code gets converted into machine code through interpretation.
[06:35] Inside the interpreter, there's already a set of equivalent machine instructions for every command or expression we write.
[06:41] For example, look at this piece of JavaScript code.
[06:43] It contains several lines of instructions.
[06:48] The interpreter already knows the corresponding machine level operations for each of these lines.
[06:52] Suppose we have a variable where two numbers are being added.
[06:55] That's an arithmetic operation.
[06:57] Then in the next line, there's a console log statement that prints something on the screen.
[07:04] And there could be many more such instructions in our code.
[07:05] For every one of these, the interpreter has a predefined binary or machine code equivalent which tells the computer.
[07:14] Exactly how to execute that particular command.
[07:16] So we can visualize it like this.
[07:19] When we run a program, the interpreter starts executing the instructions line by line.
[07:25] First, it looks at the first line where two numbers are being added.
[07:30] The interpreter already knows how to handle that operation.
[07:31] So it takes that instruction, translates it into binary and executes it.
[07:36] Once the first line is done, it moves to the next line.
[07:39] Let's say that's a console log statement.
[07:41] The interpreter knows how to execute that too.
[07:43] So it runs it.
[07:45] Then it moves to the next line and the next and so on.
[07:48] But there's a problem.
[07:51] This process is quite slow because the interpreter has to execute one line at a time, constantly going back and forth between reading the script and executing it.
[07:59] This repeated process makes pure interpretation significantly slower.
[08:03] That's why JavaScript was very slow back in the '90s.
[08:07] However, this process did have one big advantage.
[08:10] You might wonder if it was so slow, why was it designed this?
[08:15] Way?
[08:15] The reason is that JavaScript was very easy to debug at that time.
[08:20] Here's why.
[08:20] The interpreter executes one instruction at a time.
[08:24] So if it encounters an error while running any particular line, it can immediately stop and notify the programmer exactly where the problem occurred.
[08:30] This made debugging much easier because developers could instantly identify the issue, fix it and continue running the code.
[08:39] So the advantage of interpretation was that it was easy to debug.
[08:41] But the downside was that it was slow.
[08:44] To overcome that slowness, the concept of compilation was introduced.
[08:49] Let's now see how compilation works.
[08:51] A compiler works differently.
[08:53] Instead of executing code line by line, it takes the entire program at once, translates the whole thing into machine code and then runs it line by line from that compiled version.
[09:04] This makes the process much faster compared to interpretation.
[09:06] However, there's a problem here too.
[09:08] Suppose there's an error in one of the lines of code.
[09:13] The compiler won't stop there.
[09:13] It
[09:15] Will continue compiling the rest of the code.
[09:17] That means if your program contains any malicious or faulty logic, for example, an infinite loop or a piece of code that causes a memory leak, it might still get executed and potentially crash your system.
[09:31] You won't have the chance to stop it midway like in the interpreter's case.
[09:33] So that's the major drawback of compile languages.
[09:36] They can crash the system and are harder to debug.
[09:40] Why?
[09:40] Because the entire code gets compiled first.
[09:43] So we don't immediately know which exact line contains the error or bug.
[09:47] To find that out, we have to run the program first and only then can we detect where the problem is.
[09:52] In short, with interpretation, we had the advantage of easy debugging but slower execution.
[09:59] On the other hand, compilation gives us speed but makes debugging difficult and increases the risk of system crashes.
[10:06] So imagine if there were a way to combine both to get the best of both worlds.
[10:11] Fast execution and easy debugging.
[10:13] That's exactly what.
[10:16] Google Chrome did in 2008 when they introduced the V8 engine.
[10:21] They combine these two approaches into a single system and called it a just in time compiler or JIT compiler.
[10:29] This JIT compiler marges the easy debugging nature of interpretation with the high-speed execution of compilation, creating a highly optimized and balanced process.
[10:40] This approach might not be as fast as fully compiled language, but it's definitely fast enough that users can't tell the difference in real world performance.
[10:47] Now, let's see how the JIT compiler actually works.
[10:49] In J, the just in time part behaves like an interpreter.
[10:54] It reads the code while the compiler part translate it into machine code.
[10:59] Here's what happens when we write our JavaScript code.
[11:02] The J compiler doesn't immediately compile the entire thing.
[11:06] Instead, it keeps the code as it is.
[11:08] When a particular function or instruction is invoked, say for example the instruction one function, the J compiler instantly compiles that.
[11:17] Specific function into machine code, executes it and returns the result.
[11:22] Now suppose we call another function instruction two and an error occurs inside it.
[11:27] The J compiler would immediately detect it and say I have just in time compiled this function and there's an error here.
[11:33] Stop right there.
[11:36] This allows the programmer to instantly see where the issue is and fix it.
[11:41] Maintaining both speed and debugging flexibility.
[11:43] That means we can still debug easily.
[11:45] So it's both easy to debug and fast enough to run efficiently.
[11:50] In other words, it's truly the best of both worlds.
[11:53] That's the core feature of the JIT compiler.
[11:55] And this is exactly how modern JavaScript engines including NodeJS work today.
[11:59] They compile JavaScript through just in time compilation.
[12:03] I hope this part is clear to you.
[12:05] So with that understanding, let's move on to today's main topic, JavaScript's execution context.
[12:09] Many people start learning directly from execution context.
[12:14] But I personally believe it's important to first.
[12:18] Understand how the compilation process actually works.
[12:22] Now that you have that foundation, when we dive into execution context, your brain will be able to connect both parts and visualize the complete picture of how JavaScript truly operates.
[12:31] So let's move on to our main topic, JavaScript execution context.
[12:37] In my opinion, execution context is the most important concept in JavaScript.
[12:41] The reason is simple.
[12:43] If you truly understand how it works, then advanced topics like hoisting, scope, scope chain, and closures will become incredibly easy to grasp.
[12:51] Let's start by thinking about how we usually write code.
[12:53] One common strategy is to break our code into smaller parts.
[12:58] These separate pieces of code can have different names like functions, modules, or packages.
[13:04] But no matter what we call them, their purpose is the same.
[13:07] To break a complex program into smaller, more manageable chunks.
[13:12] This division reduces complexity and makes our code easier to read, maintain, and debug.
[13:17] On the screen, we can see an example.
[13:19] On.
[13:20] The left side, there's a block of code and on the right side, the same code is divided into smaller functions.
[13:24] At first glance, the code on the left might look simpler, but if you look closely, it's not really practical.
[13:31] In this example, I have represented each action with a simple console log statement just for easy understanding.
[13:38] But in a real world application, things aren't that simple, right?
[13:43] Instead of just console logs, there would be complex logic, multiple algorithms, API calls, and data processing happening behind the scenes.
[13:51] Now, imagine if we wrote all those complex operations like taking orders, processing them, and completing them all inside a single file or function.
[13:58] That would make our code neither easy to read nor easy to maintain.
[14:03] It would quickly become messy and difficult to debug.
[14:05] But if I break each task into smaller functions like the ones on the right side, it becomes much easier to maintain and the overall complexity of the code decreases significantly.
[14:16] In the same way, JavaScript also break down our code.
[14:21] Into smaller parts before interpreting it.
[14:23] This helps reduce the complexity of execution.
[14:26] Each of these smaller units is what we call an execution context.
[14:31] That's the simplest definition you can think of.
[14:32] An execution context is basically a small isolated environment where a specific piece of code is interpreted and converted into machine language.
[14:42] So to make its job easier, the JavaScript engine divides our code into smaller parts and executes them one by one.
[14:50] Each of those parts is an execution context.
[14:52] In today's session, we won't be writing any code.
[14:55] Instead, I will walk you through how execution contexts are actually created inside the JavaScript engine line by line so you can clearly visualize what happens behind the scenes when your code runs.
[15:06] Let's imagine the left side of the screen is our code editor where we will be writing the code and the right side will show how the execution context is created.
[15:15] Now, at this point, the left side, our code editor is completely empty.
[15:19] Even when.
[15:22] There's no code written yet, JavaScript still creates something called a global execution context right at the very beginning.
[15:28] So what exactly is this global execution context?
[15:30] Think of it as a simple object or you can visualize it as a box, a container that holds everything your program needs to start running.
[15:41] At the very start, before any code is executed, this global execution context is created inside it.
[15:48] The first thing we have is the window object which I'm sure you are already familiar with.
[15:55] Next, we have the this keyword which initially points to the window object.
[16:00] Then comes the variable object.
[16:02] And finally, another structure called the scope chain.
[16:05] There's one more important thing to understand.
[16:07] The global execution context actually goes through two distinct phases.
[16:11] When the program runs for the very first time, it enters what's called the loading phase or creation phase.
[16:18] During this phase, your code doesn't execute yet.
[16:21] To summarize,
[16:24] When the global execution context is first created, it contains four main components: Window, this variable object, and scope chain.
[16:33] And before any line of code runs, JavaScript first goes through the loading phase.
[16:39] Now, let's assume we have written some code.
[16:41] It's just a short seven-line script where we have a variable named topic and a function called get topic that simply returns that topic.
[16:50] As we have just discussed, when the global execution context is created, it already includes window, this, variable object, and scope chain.
[16:57] Along with that, during the loading phase, any functions and variables declared in our code like topic and get topic also get added to the global execution context.
[17:05] But here's an important point.
[17:07] During the loading phase, these variables and functions are only recognized, not yet fully executed.
[17:15] That means their values are temporarily set to undefined until the actual execution begins.
[17:20] As I mentioned earlier, there's something inside the.
[17:24] Global execution context called the variable object.
[17:26] During the loading phase, JavaScript allocates a specific space in memory for every variable declared in the code.
[17:34] So for example, a memory slot is created for our variable topic and JavaScript assigns it the value undefined at this stage.
[17:44] Similarly, the get topic function is also stored inside that same variable object.
[17:49] But instead of assigning it as undefined value, JavaScript stores a reference to the function.
[17:55] Meaning the function's entire structure or body is saved somewhere in memory.
[18:00] So now through the global execution context, JavaScript already knows that there's a function called get topic defined in the program.
[18:09] It hasn't executed it yet, but it's aware that such a function exists and might need to be called later during execution.
[18:16] Once the creation phase is complete, JavaScript moves on to the second phase, the execution phase.
[18:21] In this phase, the program starts running.
[18:25] From the very beginning, line by line.
[18:28] So when execution begins it looks at the first line v topic is equal to JavaScript execution context.
[18:36] Now JavaScript already knows the variable name topic because during the loading phase it had already stored it in memory with the value undefined.
[18:45] When the execution phase starts, JavaScript simply replaces that undefined with the actual value we have assigned.
[18:52] This is the first time the code truly gets executed.
[18:57] During the creation phase, JavaScript also stored the get topic function in its memory.
[19:02] It knows that somewhere in this program, there's a function called get topic.
[19:07] So when it reaches line 7 where the function is called, it retrieves that function reference from memory and executes it.
[19:13] Now imagine we write console.log topic at the very top of our code.
[19:19] Meaning we are trying to use the variable before it's even declared.
[19:25] In earlier tutorials on JavaScript hoisting.
[19:27] I explained this concept but not in this much detail, not in the context of the execution context.
[19:32] Back then I said that during hosting JavaScript conceptually moves all the variable declarations to the top of the scope, though only the declarations, not the actual values.
[19:45] But now through the lens of the execution context, you will understand what really happens behind the scenes.
[19:52] As we saw earlier, during the loading phase, JavaScript already stored the variable topic in memory and assigned it the default value undefined.
[20:02] That's why when we try to print topic in the very first line before its declaration, the console will show undefined, which is the value set during the loading phase.
[20:13] Then when the program enters the execution phase and reaches the line v topic is equal to JavaScript execution context.
[20:19] If we place our console log after that line, we will see the correct value.
[20:23] Right?
[20:24] That's because during execution when JavaScript encounters this line, it
[20:28] updates the value of the topic variable
[20:31] in the execution context from undefined
[20:33] to JavaScript execution context. Since
[20:37] the value is now updated, any console
[20:39] log that runs after this point will
[20:41] display the correct value. So if you
[20:44] have watched my earlier tutorial on
[20:45] hosting and now you are watching this
[20:47] video, you will finally understand why
[20:49] this hosting behavior happens inside the
[20:51] JavaScript engine. In that tutorial, I
[20:54] explained hoisting as variables being
[20:57] moved to the top, which was just a
[20:59] simplified way to help you visualize it.
[21:01] But now through execution context you
[21:04] can see what actually happens behind the
[21:06] scenes. I hope this part is crystal
[21:09] clear. Now to summarize the global
[21:12] execution context is basically a
[21:13] JavaScript object. At the very beginning
[21:16] it contains the window object if we are
[21:18] in a browser environment or the global
[21:21] object if it's NodeJS. There's also a
[21:24] this object that points to the same
[21:26] window or global. Then all the variables
[21:29] declared in our code are stored inside
[21:32] something called the variable object and
[21:34] any functions we define are also stored
[21:36] there but only as references. Meaning
[21:40] JavaScript just gives a pointer to their
[21:42] full body in memory. Then when the
[21:45] program enters the execution phase and
[21:46] reaches the line v topic is equal to
[21:48] JavaScript execution context. If we
[21:51] place our console log after that line,
[21:53] we will see the correct value. Right? It
[21:56] also keeps something called a scope
[21:57] chain. As we know, JavaScript uses
[22:00] lexical scoping, meaning each function
[22:02] knows exactly where it was written and
[22:05] can access variables from its outer
[22:07] scopes. So, JavaScript maintains all
[22:09] these scopes together inside the scope
[22:11] chain. That was the visual
[22:13] representation of the global execution
[22:15] context. Now, let's go a bit deeper. By
[22:18] this point, you have already understood
[22:20] how the global execution context works.
[22:23] The good news is that after this you
[22:26] only need to understand one more type of
[22:28] execution context, the function
[22:29] execution context. And the interesting
[22:32] thing is it's almost identical to the
[22:34] global execution context. The only
[22:36] difference is that it's created every
[22:38] time a function is called. Let's quickly
[22:41] recall what happened inside the global
[22:43] execution context. It created a global
[22:45] object, created a this object, allocated
[22:48] memory for variables and functions, and
[22:51] initially assigned all variables the
[22:53] value undefined. Right? Now, if you
[22:56] think carefully, which of these four
[22:58] steps do you think won't be necessary
[22:59] when a function gets executed? Exactly.
[23:03] The global object shouldn't be created
[23:05] again inside a function execution
[23:07] context because the entire program
[23:09] already has one global object that was
[23:11] created earlier and all functions can
[23:14] access it when needed. So instead of
[23:16] creating a new global object, something
[23:18] new happens inside the function
[23:20] execution context. It creates an
[23:22] arguments object. This object holds all
[23:25] the parameters passed to that function.
[23:27] That means whenever we define a function
[23:29] in JavaScript and it has parameters,
[23:31] JavaScript automatically creates an
[23:33] object called arguments inside that
[23:35] functions execution context to store
[23:37] those values. If we write console.log
[23:40] arguments inside the function body, you
[23:42] will see all the past parameters neatly
[23:44] stored as key value pairs within that
[23:47] arguments object. So the only structural
[23:50] difference between the function
[23:51] execution context and the global
[23:53] execution context is this. The global
[23:55] one contains a global object while the
[23:58] function one contains an arguments
[24:00] object. Everything else remains exactly
[24:02] the same. Simply put, think of the
[24:05] global execution context as an entire
[24:07] world. Whenever you call a function, a
[24:10] new world is created inside that global
[24:12] world following the same structure and
[24:14] behavior. I hope that makes the idea
[24:17] clear. When a function is called,
[24:19] JavaScript creates a completely separate
[24:21] execution context for it. Inside that
[24:23] context, it builds an arguments object
[24:26] to hold all the parameters, creates a
[24:28] this object as usual and allocates
[24:30] memory for all the variables and inner
[24:33] functions defined inside that function.
[24:35] That means hosting also applies inside
[24:38] functions. During the creation phase,
[24:40] JavaScript will assign undefined to all
[24:43] variables within that function. And
[24:45] that's exactly why hosting happens there
[24:47] too. And this is how the function
[24:49] execution context works. Now remember
[24:52] how we said that in JavaScript one
[24:54] function can contain another function.
[24:56] That means there can be multiple
[24:58] execution context. The global execution
[25:00] context is always there and alongside it
[25:03] multiple function execution contexts can
[25:06] be created as different functions are
[25:08] called. So obviously JavaScript needs a
[25:11] way to keep track of all these contexts
[25:13] somewhere. And since JavaScript is a
[25:15] single-threaded language, meaning
[25:17] everything runs on one main thread, that
[25:19] same thread is also responsible for
[25:21] managing all these execution contexts,
[25:23] creating them, stacking them, and
[25:25] clearing them one by one. JavaScript
[25:28] actually stores these contexts one on
[25:30] top of another just like a stack. And
[25:32] this stacking structure is called the
[25:35] execution stack or more commonly the
[25:37] call stack. At the very beginning,
[25:39] JavaScript places the global execution
[25:41] context at the bottom of the stack. Then
[25:44] whenever a function is called, a new
[25:46] function execution context is created
[25:47] and pushed on top of it. If that
[25:50] function calls another function inside
[25:52] it, another context is stacked above
[25:54] that and so on. In this way, JavaScript
[25:57] keeps adding and removing execution
[25:59] contexts in a stacked order as the
[26:01] program runs. So now we have a complete
[26:04] picture of what an execution context
[26:06] really is. It's made up of two types.
[26:08] The global execution context, the
[26:10] function execution context. Now, let's
[26:13] move on to something a bit more
[26:14] advanced. So far, we have only seen how
[26:17] the execution context works in the
[26:19] global scope or when a function is
[26:21] called directly from the global scope.
[26:22] But what if we have multiple functions
[26:25] or if one function contains another
[26:27] function? In other words, nested
[26:29] functions. How does the execution
[26:32] context behave then? Because every time
[26:34] JavaScript creates an execution context,
[26:36] it needs to keep track of them somewhere
[26:38] in memory. There has to be some logic to
[26:41] determine which context should run first
[26:44] and which one should execute next. To
[26:47] manage all of that, JavaScript needs to
[26:49] maintain a specific data structure,
[26:52] right? And that data structure is called
[26:54] the execution stack. It's actually based
[26:57] on a stack data structure. Think of it
[26:59] like stacking books on a shelf. You
[27:02] place one book on top of another.
[27:04] Similarly, JavaScript stacks each
[27:06] execution context on top of the previous
[27:08] one inside the execution stack. Now, a
[27:11] stack has one special property. The last
[27:14] item that goes in is always the first
[27:16] one to come out. In short, it follows
[27:18] the LEO rule that means last in first
[27:21] out. All right, let's connect this idea
[27:24] with our code. We know that whenever we
[27:27] run a JavaScript program, the global
[27:28] execution context is created first.
[27:31] Whether our file contains code or not,
[27:33] this global context will always be
[27:35] created automatically. And once it's
[27:37] created, JavaScript places this global
[27:39] execution context at the very bottom of
[27:41] the execution stack. That's where
[27:43] everything begins. Next, let's look at
[27:46] our code. Inside it, we have three
[27:48] things. A variable declared with var a,
[27:52] a function called one with its
[27:54] definition, and finally a call or
[27:56] invocation of that function. These three
[27:59] the variable a the function one and its
[28:01] invocation all exist in the global scope
[28:04] or the root level of the program. Inside
[28:07] the body of the one function there's
[28:09] another nested function called two and
[28:11] inside two there's yet another function
[28:13] called three. These nested functions are
[28:15] not part of the global scope. They
[28:17] belong to their respective inner scopes.
[28:20] So during the creation phase, JavaScript
[28:22] stores the variable a with the initial
[28:24] value undefined and registers the body
[28:27] of the function one with the variable
[28:29] object. Since there's nothing else in
[28:31] the global scope, JavaScript then moves
[28:33] on to the execution phase. In this
[28:36] phase, execution starts line by line.
[28:38] First, it updates the value of a from
[28:41] undefined to one. Then it moves to the
[28:44] next line where it finds the function
[28:45] definition of one. Since that function's
[28:48] reference is already stored in the
[28:50] variable object, JavaScript skips over
[28:52] the function body for now and continues
[28:54] to the next line, the point where one is
[28:57] actually invoked. As soon as the one
[29:00] function is invoked, JavaScript creates
[29:02] a brand new function execution context
[29:04] inside the execution stack placed right
[29:07] above the global execution context. When
[29:09] this new context for one is created, it
[29:12] first enters its loading phase. Since
[29:15] the one function doesn't take any
[29:17] arguments, the arguments object inside
[29:19] this execution context will remain
[29:20] empty. Then comes the this reference
[29:23] which points to the global object. In
[29:26] this case, the window object. After
[29:28] that, we have the scope chain and the
[29:30] variable object. Inside the variable
[29:32] object, there's only the two function.
[29:34] Why? Because inside the body of one,
[29:37] there are no local variables declared.
[29:39] Only a console log statement that tries
[29:41] to print a. But since the variable a is
[29:45] not defined inside one, it doesn't
[29:47] appear in this context variable object.
[29:50] Instead, JavaScript will later look for
[29:52] it in the outer scope using the scope
[29:55] chain. However, the one function body
[29:58] does contain another function
[29:59] declaration, the two function. So during
[30:02] the loading phase, JavaScript stores
[30:04] that two function inside the variable
[30:06] object just like it stored the function
[30:08] definitions earlier in the global
[30:09] execution context. It follows the exact
[30:12] same process only this time it's
[30:14] happening inside the function's own
[30:16] scope. All right. Now let's check if
[30:19] there's anything else inside the body of
[30:21] one function that needs to be added to
[30:23] its variable object. The answer is no.
[30:26] There's nothing else. So next JavaScript
[30:29] moves to the execution phase. And just
[30:31] like in the global execution context, it
[30:34] starts executing line by line. The first
[30:36] line inside the one function is console
[30:39] log A. At this point, JavaScript checks
[30:41] whether the variable A exists inside the
[30:44] variable object of the current execution
[30:46] context. It looks and finds nothing.
[30:50] Since the variable A isn't declared
[30:52] inside the one function, JavaScript
[30:53] moves to the next step. It follows the
[30:55] scope chain to look into its parent
[30:58] scope. Now, what's inside the parent
[31:00] scope? Yes, the variable A is there
[31:03] defined in the global execution context.
[31:06] So JavaScript retrieves that value and
[31:08] prints it in the console. It's important
[31:10] to clearly understand one thing here.
[31:12] The scope chain is essentially a lexical
[31:15] environment. This means every scope is
[31:17] connected to its parent scope in a
[31:19] linked structure. The term chain is used
[31:21] because each scope holds a reference to
[31:24] its parent or ancestor scopes forming an
[31:27] actual chain-like connection. That's why
[31:29] when JavaScript couldn't find the
[31:31] variable A in the one functions own
[31:33] variable object, it followed that change
[31:35] upward to its parent or ancestor scopes
[31:38] and successfully found a in the global
[31:41] scope. Hopefully that part is clear. Now
[31:44] let's go back to the code flow again.
[31:46] After printing the value of a, the
[31:48] program moves to the next line. There it
[31:50] finds the body of the two function. But
[31:52] at this stage, JavaScript doesn't need
[31:54] to do anything with it because during
[31:56] the loading phase, the reference to two
[31:58] has already been stored in memory. So,
[32:01] it skips over the function body and
[32:03] moves to the next line where it sees
[32:05] that two is being invoked. Since the two
[32:08] function is now being called, JavaScript
[32:10] creates a brand new function execution
[32:12] context for it and places it on top of
[32:14] the execution stack. Just like before,
[32:17] when this new two execution context is
[32:20] created, it first goes through its
[32:22] loading phase. During this phase, it
[32:25] populates its scope chain, meaning it
[32:27] links itself with the variable objects
[32:29] of its parents or ancestor scopes.
[32:31] Inside the body of two, there's a
[32:34] variable named B. So in this new context
[32:36] variable object, JavaScript stores B
[32:39] with the initial value undefined. Then
[32:42] it finds another function definition
[32:44] three. So just like before, JavaScript
[32:47] stores the reference to the three
[32:48] function inside the variable object of
[32:51] two. Then in the next line, the program
[32:53] moves to where the function three is
[32:55] called. Since it's still in the loading
[32:58] phase, there's nothing to execute yet.
[33:01] That part is done. So now it enters the
[33:03] execution phase and starts running each
[33:05] line one by one. Inside function 3,
[33:08] what's the first line? It's a console
[33:10] log of the variable B. But that variable
[33:12] hasn't been initialized yet. In this
[33:15] context, its value is still undefined.
[33:18] So when the program runs that line, it
[33:20] prints undefined. Now you can clearly
[33:23] see why during hoisting variables often
[33:26] print undefined. Once you understand how
[33:28] the execution context work, many other
[33:31] tricky behaviors of JavaScript starts to
[33:33] make sense. So pay close attention and
[33:36] try to grasp these terms deeply. All
[33:38] right. After that in the next line the
[33:41] variable's value is being assigned. So
[33:43] the value of B changes from undefined to
[33:46] two. Then in the following line we see
[33:48] the definition of the three function.
[33:50] The execution context doesn't need to do
[33:52] anything here. So it simply skips over
[33:54] this line. The next line shows that the
[33:57] three function is being invoked and not
[33:59] just that it's being called with an
[34:01] argument four. Since the three function
[34:03] has been invoked, a brand new execution
[34:05] context is created inside the execution
[34:08] stack. Now pay close attention here.
[34:10] During the loading phase of the three
[34:12] execution context, we can already see
[34:14] that there's a value inside the
[34:15] arguments at index zero. It's four.
[34:19] That's because when the function was
[34:20] called, the value four was passed in.
[34:23] And here's something interesting. Look
[34:25] at how we named the parameter inside the
[34:27] three function. It's called D. Right? So
[34:30] during the loading phase itself, a
[34:32] variable named D appears inside the
[34:34] context and it's already assigned the
[34:36] value four. Then it moves to the next
[34:39] line. There's a console log statement.
[34:41] Nothing special happens there yet. After
[34:44] that, we have let C is equal to three.
[34:46] And this is where things get a bit
[34:48] different. You see, although var, let
[34:51] const are all used to declare variables,
[34:54] their behaviors are not the same. For
[34:56] var during the loading phase, JavaScript
[34:59] automatically allocates memory and sets
[35:01] it value to undefined. But for latent
[35:04] const JavaScript still allocates memory
[35:06] during the loading phase. The difference
[35:09] is they remain inside what's called the
[35:11] temporal dead zone or TDZ until the
[35:14] actual line of code where they are
[35:16] declared is reached. That means even
[35:19] though they exist inside the execution
[35:21] context, they can't be accessed through
[35:22] the variable object yet. Now you might
[35:25] wonder what exactly is the temporal dead
[35:28] zone. It's the period between a variable
[35:30] being created in memory and being
[35:32] initialized with a value. During this
[35:35] time, the variable technically exists,
[35:37] but since no value has been assigned,
[35:39] JavaScript keeps it temporarily
[35:41] inaccessible. If you try to access that
[35:43] variable during the TDZ, you will
[35:45] immediately get a reference error. In
[35:48] simple terms, the program knows that the
[35:50] variable exists, but it's not ready to
[35:52] be used yet. And until the code
[35:54] execution reaches that specific
[35:55] declaration line, you won't be able to
[35:57] access it from the variable object
[35:59] either. You can think of it like an
[36:01] iPhone's locked screen. The phone is
[36:03] there, but until you enter the pin and
[36:05] unlock it, you can't do anything with
[36:07] it. Now, many people wonder, doesn't the
[36:10] same thing happen with var? Why does
[36:12] JavaScript assign undefined to variables
[36:14] declared with var but keeps let and con
[36:16] inside the TDZ instead of doing the
[36:18] same? Excellent question. The main
[36:20] reason is that var comes from the older
[36:23] versions of JavaScript, especially ES5
[36:25] and earlier where there was no concept
[36:28] of safety checks or the temporal dead
[36:30] zone. Back then, JavaScript didn't want
[36:32] the program to crash if a variable was
[36:34] accessed before initialization. So to
[36:37] avoid breaking the program, it would
[36:39] automatically assign undefined as a
[36:41] fallback value. Just tried to be a bit
[36:44] more smart. But latent cost were
[36:47] introduced in ES6 where the goal was to
[36:50] keep the language safer and more
[36:52] predictable. If JavaScript had assigned
[36:54] undefined to them as well, it would have
[36:56] created the illusion that the variable
[36:58] was properly initialized even though it
[37:00] wasn't. So, JavaScript intentionally
[37:03] blocks access to those variables during
[37:05] that time to signal to developers. The
[37:08] variable exists, but it's not ready to
[37:10] be used yet. Hope that clears things up.
[37:13] Now, let's get back to our flow. At this
[37:15] point, inside the three function,
[37:17] there's nothing else left to load. So,
[37:19] it moves to the execution phase. In the
[37:22] first line, we have a console log
[37:24] printing C plus D. We already know that
[37:26] the value of D is four, but the variable
[37:29] C is still inside the temporal dead
[37:31] zone, meaning it can't be accessed yet.
[37:34] So, this line will throw a reference
[37:36] error because the program can't
[37:38] reference C from memory at that point.
[37:40] However, if we had written the code the
[37:42] other way around, first declaring let C
[37:45] is equal to three and then logging it in
[37:47] the next line, the behavior would have
[37:49] been completely different. In that case,
[37:51] during the creation phase, C would still
[37:54] start in the dead zone. But by the time
[37:56] the execution reached that line, its
[37:58] value would already be assigned. Then
[38:01] when the console log ran, both C and D
[38:04] would be accessible and their values
[38:06] would print correctly. Clear now? So you
[38:09] can see how JavaScript actually works
[38:11] behind the scenes. We have also learned
[38:13] how hosting truly operates at a machine
[38:15] level through the concept of the
[38:17] execution context. All right. Now let's
[38:20] move forward inside the three function.
[38:22] Once the console log runs successfully,
[38:24] it will print seven. After that, is
[38:26] there anything else left in the
[38:28] function? No, there isn't. Since there
[38:30] are no more lines to execute, the work
[38:32] of the three function is complete. And
[38:34] whenever a function finishes its job, it
[38:37] immediately gets popped out from the
[38:39] execution stack. That means since the
[38:41] three function has finished executing,
[38:43] it will be removed from the stack
[38:45] following the leo. That means last in
[38:48] first out rule. Now who's at the top of
[38:50] the stack? The two function. If we check
[38:53] the body of two, do we see any remaining
[38:55] lines to execute? No, it's done two. So
[38:59] two will also exit from the execution
[39:01] stack. Finally, since there's nothing
[39:04] left to run in one, that function will
[39:06] also pop out, leaving the execution
[39:08] stack completely empty. Since there are
[39:10] no more functions left to execute, the
[39:12] global execution context will also exit
[39:14] from the execution stack. However, if
[39:17] there had been more code to run in the
[39:18] global scope, for example, another
[39:20] function called write after one, then
[39:22] the global execution context wouldn't
[39:23] have been removed yet. Instead, it would
[39:26] have continued executing the next
[39:27] function just like before. I hope by now
[39:30] you have no doubts left about execution
[39:32] context. While learning about it, we
[39:34] also discovered how hosting actually
[39:36] works, didn't we? Now let's move on to
[39:39] the another important concept, scope. We
[39:42] have already discussed scope before, but
[39:44] this time we will understand how scope
[39:46] works in relation to the execution
[39:48] context. Take a look at this simple
[39:50] example. Here we have a function called
[39:52] hello. Inside it there's a variable
[39:54] named message declared with var which
[39:57] holds a certain value. Outside the
[40:00] function that is in the global scope we
[40:02] are calling or invoking the hello
[40:04] function and in the next line we are
[40:06] trying to print the message variable
[40:07] using console log. It's a very simple
[40:10] setup. So based on everything we have
[40:12] learned so far what will happen if we
[40:14] run this program? First the global
[40:16] execution context will go through its
[40:17] creation phase. During this phase, it
[40:20] will find a function named hello and
[40:22] store its reference inside the variable
[40:24] object. Once that's done, there's
[40:26] nothing else left for the creation
[40:27] phase. Right? Perfect. Now the execution
[40:30] phase begins. The first line is the
[40:32] definition of the hello function. Since
[40:35] we are in the execution phase, there's
[40:36] nothing to execute here. The functions
[40:38] reference is already stored in memory.
[40:40] So the program moves on to the next
[40:42] statement where the hello function is
[40:44] invoked. As soon as that happens, a new
[40:47] execution context for the hello function
[40:49] is created. Inside the creation phase of
[40:52] the hello functions execution context,
[40:54] the variable message is placed inside
[40:56] the variable object with the value
[40:58] undefined. Once the creation phase is
[41:00] complete, the program moves to the
[41:02] execution phase where the variable
[41:04] message gets its actual assigned value
[41:07] instead of undefined. So in the
[41:09] execution phase, the value of the
[41:11] message variable becomes hello world.
[41:14] After that, does the hello function have
[41:16] anything else to do? No, it doesn't. So,
[41:18] the hello functions execution context
[41:20] will now be popped off or destroyed.
[41:22] That means the program returns to the
[41:24] global execution context. Now, what
[41:27] happens next? After the hello function
[41:29] is invoked, the next line is console.log
[41:32] message. So, the program will now try to
[41:35] print message. But wait, is the message
[41:37] variable declared inside the global
[41:39] execution context variable object? No,
[41:43] it isn't. That variable was created
[41:45] inside the function execution context.
[41:48] And when that function finished
[41:49] executing, its execution context along
[41:52] with its scope chain and variable object
[41:54] was completely removed from memory.
[41:57] That's why when the console tries to
[41:59] access message, JavaScript throws a
[42:01] reference error. Can you relate this?
[42:03] Now, those of you who have seen my video
[42:05] on scope might remember the example I
[42:08] gave. A child can always access or
[42:11] inherit things from its parent. But a
[42:13] parent can never access what belongs to
[42:15] the child. Back then it was just an
[42:18] example. But now you can actually
[42:20] visualize how the program manages all of
[42:22] this behind the scenes. That means while
[42:25] understanding execution context we have
[42:27] rediscovered the concept of scope in a
[42:30] much clearer and more practical way. Now
[42:33] let's move on to another very important
[42:35] topic closure. Just like we visualized
[42:38] execution context earlier, we will now
[42:40] see how a closure is created and how it
[42:42] works step by step. First, the program
[42:45] creates the global execution context. We
[42:47] are now in the creation phase. During
[42:49] this phase, the program scans through
[42:51] the code and finds a variable named sum
[42:53] and a function named do sum. So, memory
[42:56] is allocated for sum and its initial
[42:58] value is set to undefined. The entire
[43:01] function definition of do sum is stored
[43:03] in memory as a reference. Once the
[43:06] creation phase is complete, the
[43:07] execution phase begins.
[43:10] In the execution phase, the first line
[43:12] sets sum is equal to zero. Next, the
[43:14] program skips over the do some function
[43:16] since it's only a definition. Then it
[43:18] moves to the line v temp is equal to do
[43:20] sum in the bracket 2. Here the function
[43:22] is being called. So a brand new function
[43:24] execution context is created for doum.
[43:27] Now the creation phase of that function
[43:29] execution context begins. The parameter
[43:31] a receives the value two. So in memory
[43:34] we have a is equal to 2. Inside the
[43:36] function there's an anonymous function
[43:38] being returned. That function's
[43:40] reference also gets stored in memory.
[43:42] Once the creation phase is done the
[43:44] execution phase starts. During this
[43:47] execution phase the do some function
[43:48] doesn't perform any calculation. Instead
[43:51] it simply returns that anonymous
[43:52] function. When that happens the do some
[43:55] execution context is popped off the
[43:56] stack. But something very important also
[43:58] occurs at that exact moment. A closure
[44:01] is created. Why does that happen?
[44:04] Because when the anonymous function is
[44:05] returned, it still remembers the data
[44:08] from its outer scope. In this case, a is
[44:10] equal to two. Even though the do some
[44:13] function itself gets destroyed, its
[44:14] lexical environment doesn't disappear
[44:17] completely. JavaScript realizes that
[44:19] this inner function might be called
[44:21] again later. And when that happens, it
[44:23] will need access to those outer
[44:25] variables. So it preserves that
[44:28] environment in a separate structure
[44:29] called the closure scope. Now you can
[44:32] understand JavaScript is really smart.
[44:34] Next the code moves to the line sum is
[44:36] equal to sum plus temp 8. Here the
[44:40] function is called again meaning temp 8
[44:42] is invoked. This creates a new execution
[44:44] context for the anonymous function.
[44:46] During its creation phase the parameter
[44:48] B is assigned the value 8. When the
[44:51] execution phase begins the function
[44:53] executes return A + B. Now since a
[44:57] doesn't exist in the current scope,
[44:59] JavaScript follows the scope chain and
[45:01] looks into the outer scope which is the
[45:03] closure scope. There it finds a equal to
[45:06] 2, performs the calculation 2 + 8 is
[45:09] equal to 10 and returns the result 10.
[45:12] At that point the temp functions
[45:14] execution context completes its job and
[45:16] gets popped off from the stack. So in
[45:19] the line sum is equal to sum plus temp
[45:21] 8, the value of sum was initially zero.
[45:23] That means it becomes sum is equal to 0
[45:26] + 10. Now where did that 10 come from?
[45:29] It came from the temp functions
[45:31] execution context. Now you can clearly
[45:34] see how a closure actually works. A
[45:36] closure is basically a mechanism that
[45:39] keeps a function connected to its outer
[45:41] environment even after the outer
[45:43] function has been destroyed. This is one
[45:45] of JavaScript's most magical yet
[45:48] completely logical behaviors and it all
[45:51] originates from the concept of the
[45:53] execution context itself. So everything
[45:56] you have seen so far, this is what
[45:58] JavaScript's execution context truly is.
[46:01] By understanding just this one topic, we
[46:03] have been able to uncover how JavaScript
[46:06] actually works behind the scenes. We
[46:08] discovered how scopes are formed and how
[46:10] confusing topics like closure and
[46:12] hoisting are really operate logically
[46:15] within the language. All this time we
[46:17] used to just repeat what we heard that
[46:20] hoisting means variable are lifted to
[46:22] the stop and closure means a function
[46:24] stays connected to its outer
[46:26] environment. But after watching this
[46:28] video you should now have a clear inside
[46:30] out understanding of how and why
[46:32] JavaScript behaves this way. From today
[46:35] you may not understand your wife but you
[46:37] will definitely understand JavaScript. I
[46:40] have given my best effort to explain
[46:41] this topic as clearly as possible. If
[46:44] this video helped you understand these
[46:45] concepts better, please don't forget to
[46:47] like and comment. It truly encourages me
[46:50] to keep making more videos like this.
[46:52] And if you have any valuable feedback or
[46:55] specific topics you would like me to
[46:57] cover next, feel free to let me know.
[46:59] Your small words of motivation inspired
[47:02] me to keep creating and sharing more.
[47:04] Thank you so much for everything and
[47:06] thank you so much for watching my
[47:08] videos. I will see you again in another
