Playing Role of Compiler and Interpreter in JavaScript
Let's examine the nature of JavaScript as both compiled and interpreted. Most people take it as a given that JavaScript is an interpreted language. So what does "interpreted" actually mean — when you write a program in a language like C or Java, there's a compilation step that has to happen before you execute your program. That step converts your source code into an intermediate form that you then run.
You don't actually execute the source code itself; you execute this intermediary form that's been compiled.
The Compilation Step
In JavaScript, the compilation step only worries about var declarations — it does not care about the right-hand side or the value of that variable. The compilation step examines the variable declarations and registers them in that scope.
var a = 10;
var b = 20;
console.log(a + b);
Take a look at the code above — 2 variables declared in global scope, a and b, with values 10 and 20. The compilation step will register these 2 variables in the global scope only; it does not care about other lines of code as far as variable declaration goes. It looks at the first line, sees variable a, and registers it in global scope, then moves to the second line, sees variable b, and registers it in global scope too.

Let's take another example and examine how the compiler registers these variables in memory.
var a = 10;
function myFunction() {
var b = 7;
var c = b;
console.log(a + b);
}
myFunction();
If you look at the code, can you identify how many variable declarations are happening here? If you answered 3, well — you're missing one variable.
Apart from a, b, and c, there is one more variable: the function declaration named myFunction. In JavaScript, functions are objects — the moment you create a function, you're actually creating a variable that holds that object. So there's a fourth, hidden variable declaration: myFunction.

As you can see above, if we play the role of the compiler, it starts from the top — it's only interested in the left-hand-side variable declaration var a, so it registers a in the global scope like before. Then it reaches line three and sees the function declaration myFunction, so it registers a variable called myFunction; it knows it's a function, but for variable purposes it's just another variable bound to that name. Now it comes to var b — since b exists inside a function, it registers b in the scope of myFunction rather than the global scope. Similarly for var c. Other lines don't have variable declarations, so it ignores them.
function foo(name) {
console.log("hello, " + name);
}
foo('myName');
Take a look at function foo — one more hidden declaration happens here without the var keyword: the argument name. Arguments also get registered in function scope.
So this is how the compiler registers these variables and their scopes.
Interpretation Step (Execution Step)
The interpretation step doesn't worry about var or declarations. Instead, it looks at what it needs to do. When it needs to find which variables to use, it looks at the scope chain that was created during the compilation step.
var myName = 'ashu';
function greet(name) {
console.log("hello, " + name);
}
greet(myName);
Here is the compilation step...

Now let's play the role of the execution process and look at how it handles this code. On the first line, var myName = 'ashu';, it takes the string value and assigns it to the variable. Which scope's variable does it assign to? The compilation chain is already done, and this statement executes in global scope, so it checks: does global scope have a variable named myName? Yes — so it assigns the string to it.
Next there's a function declaration, and nothing happens there. The last line executes the function call greet(myName). The execution context is still global scope, so the interpreter checks the global scope for a variable called greet — found. The next variable it needs is myName, again found in global scope. It calls greet, passing myName as the argument.
When the function runs, there's an implicit assignment: the parameter name is assigned the value passed to it — ashu. This assignment happens inside the scope of greet, so it checks: does greet's scope have a variable called name? Yes — it assigns ashu to it. Then it executes console.log("hello, " + name). console is an object reference available in the global scope, so it checks greet's scope first — not found — then goes one level up to global scope, where console exists as part of the JavaScript runtime. It calls console.log, resolving name again from greet's scope, concatenates "hello, " + name into "hello, ashu", logs it, and the function call ends.
Think of execution as a two-phase process: first the compilation step, where scope chains are created and variables are registered into the appropriate scope; then the interpretation step, where variables are resolved by walking up the scope chain until they're found — starting from the current scope and going up to the global scope if necessary.
A note on let and const
Everything above uses var, since that's what makes the two-phase model easiest to see. let and const go through the same compilation-step registration, but with a twist: the variable is registered in its block scope (not just function/global scope), and it stays unreachable — in the "temporal dead zone" — from the top of that block until the actual declaration line executes. That's why accessing a let/const variable before its declaration throws a ReferenceError instead of quietly returning undefined the way a hoisted var does. Same compiler/interpreter model, just a stricter rule about when a registered variable is allowed to be touched.