Before JavaScript runs a single line of your code, it creates an invisible container called the execution context with two sides: memory and code.
Complete lesson & earn 250 PX
EXERCISE
1Before JavaScript executes a single line of your code, it creates an invisible container called the execution context. Understanding this container is the key to understanding everything else.
EXERCISE
2The memory side of the execution context is where JavaScript stores every variable and function as a key-value pair, like entries in a phone book.
EXERCISE
3The code side of the execution context is the actual workbench where JavaScript reads and runs your code, one line at a time, top to bottom.
Something happens between the moment you save a JavaScript file and the moment the first line runs, and most developers never find out what. The engine does not simply start at line one. It builds a first, fills part of it in, and only then begins executing. That container is called the execution context, and almost every confusing JavaScript behaviour you meet later is a consequence of how it works.
The context has two sides, and they do their jobs at different times. The memory component holds every variable and function in the current scope as a name-and-value pair. The code component is the part that actually runs statements, one at a time, in order. The specification calls these the Variable Environment and the Thread of Execution, and you will meet both names in articles and interview questions.
A small program, and the container built to hold it:
var name = "Sarah";
var age = 25;
function greet() {
console.log("Hello!");
}
greet();
Four statements. Before any of them run, has already built a context that knows name, age and greet exist. The memory side has all three names. The code side has not started.
That split is not academic. It is why the following code does not do what its layout suggests:
console.log(age); // undefined, and not an error
greet(); // "Hello!", works perfectly
var age = 25;
function greet() {
console.log("Hello!");
}
Both lines run before the declarations they depend on, and they behave differently from each other. age is known but empty. greet is known and complete. Neither one throws. That asymmetry is not a quirk to memorise, and it is not a bug you should work around. It falls directly out of how the memory side gets filled before the code side starts, which is exactly what the next lesson takes apart.
Pro Tip: JavaScript is single-threaded and synchronous: one statement at a time, each finishing before the next begins. That is not a limitation to route around, it is the guarantee that lets you trace a bug with your finger and trust the order things happened in.
Before your code starts running, JavaScript reads through it and reserves a slot for every variable and every function it finds. Nothing has a real value yet. The engine is taking inventory: noting what names exist so it knows what it is dealing with. This is the memory component, and it is filled in completely before a single statement executes.
The slots are name-and-value pairs, and the rule for what goes in the value half is where beginners and intermediate developers part ways. A variable declared with var gets the placeholder undefined. A function declaration gets its entire body, stored whole. Not a reference to be resolved later, not a name to look up. The actual function, ready to call.
The same declarations, and what memory holds before execution begins:
var score = 100;
var player = "Alex";
function celebrate() {
console.log("You won!");
}
At this point in the process score is undefined, player is undefined, and celebrate is the complete function. The literal values 100 and "Alex" have not been assigned to anything. They are still text in your source file, waiting for the code component to reach them.
You can prove this rather than take it on faith. Both reads below happen before their declarations, and the difference in what comes back tells you exactly what the memory side did:
console.log(typeof celebrate); // "function"
console.log(typeof score); // "undefined"
celebrate(); // "You won!"
var score = 100;
function celebrate() {
console.log("You won!");
}
The function is callable. The variable is a name with nothing behind it.
This also explains a distinction that trips people up in error messages. A name that was declared somewhere has a slot, even if that slot is empty. A name that was never declared anywhere has no slot at all, and JavaScript reports those two states differently:
console.log(declared); // undefined
console.log(neverExisted); // ReferenceError: neverExisted is not defined
var declared = 1;
undefined means the engine found a slot and it was empty. ReferenceError means the engine looked for a slot and there was none to find. Those are different failures with different fixes, and reading them as the same thing will cost you time in a debugger.
If the memory component is the inventory, the code component is the worker. It starts at the first statement, finishes it, moves to the second, and continues to the end. One statement at a time, in the order written, with nothing running alongside it. The specification calls this the Thread of Execution, and JavaScript has exactly one of them.
That word one is doing real work. Languages with several threads can have two pieces of code touching the same variable at the same moment, which is a category of bug that is genuinely hard to reason about. JavaScript removes that whole category by construction. The cost is that a slow statement blocks everything behind it; the benefit is that you can read a function top to bottom and trust the order.
Execution is sequential, and the order is never in doubt:
var total = 0;
total = total + 10;
total = total + 20;
console.log(total); // 30
Four statements, four steps, always in that order. There is no scheduler that might reorder them and no second worker that might interleave. If you can read the lines, you can predict the output.
That predictability is what makes the next example legible rather than mysterious:
var a = 5;
console.log(a); // 5
var b = a * 2;
console.log(b); // 10
a = 100;
console.log(b); // still 10, not 200
The last line catches people out, and it is worth being precise about why. When b was assigned, the code component evaluated a * 2 right then, got 10, and stored that number. It did not store a relationship to a. Reassigning a afterwards changes the slot named a and nothing else. The thread had already moved past that line and has no reason to revisit it.
The flip side of a single thread is worth seeing rather than being told about. Because there is only one worker, a statement that takes a long time does not run in the background. It stops everything:
console.log("first");
var end = Date.now() + 3000;
while (Date.now() < end) {
// deliberately busy for three seconds
}
console.log("second");
In a browser, that loop freezes the page. Clicks do not register and animations stop, because the one thread is stuck inside the loop and nothing else can run until it finishes. This is the reason asynchronous code exists at all, and it is the problem Unit 6 sets out to solve.
An execution context is a container with a memory side (variable environment) and a code side (thread of execution). JavaScript creates one for every program and every function call.