You don’t know Lexical Scope

Often times new developers when try to understand the depth of JavaScript they learn new jargon which confuses them if not scare them.
So today, we are going to understand one of the most common yet easy to understand Lexical Scope.
A little definition about Scope from the book “You Don’t know JS”
Scope is the set of rules that determines how JS Engine can lookup a variable by its identifier name and find it, either in the current scope or any nested scope.
So, basically there are 2 models for how scope works.
- Lexical Scope → Used in most Programming Languages
- Dynamic Scope → Used rarely and in bash scripting.
Lexical Scope and its Meaning
Traditionally, in compile type language your source code goes to 3 main processes before its execution and that is called compilation.
These process are:
Tokenizing/Lexing → Parsing → Code-Generation
So, this Lexical scope comes from this lexing process, which breaks your code into smaller tokens and then pass it to parsing stage where Abstract Syntax Tree (AST) is created from the tokens and then this AST goes to final step to turn itself into an executable code.
(There are much more for the above mentioned steps, but for sake of this article we will keep only the required parts.)
Now, this lexical Scope is defined at Lexing time, meaning it gets based on where variables and blocks are written by you in code. Let’s take a look on below code.
function grandParent(grandParentParam) { var grandParentVariable = 10; function parent() { var parentVariable = 20; function child() { var childvariable = 50; console.log(grandParentParam, grandParentVariable, parentVariable, childvariable); } child(); } parent();}grandParent(4);
I am also attaching same code snip too for more clarity.

In above code, there is a child
function nested inside parent
which is again nested inside grandParent
function. And, all 3 functions have their respective variable as well, which are childVariable, parentVariable & grandParentVariable
. And, an argument is passed onto grandParent
function which can be seen on line number 1 as grandParentParam
Scope 1 → Now, you see line number 13, function execution is happening for grandParent(4)
that is in global scope.
Scope 2 → Another nested scope start from line 1–12. i.e scope of grandParent
function.
Scope 3 → Scope from line 3–10 is for parent
function.
Scope 4 → Scope from line 5–8 is for child
function.
Now, if we execute the code, we get result as 4 10 20 50
because of console.log present on line number 7.
Look-ups in Scope
Now, we will understand how the variable look-ups happen in above code.
When engine executes console.log(grandParentParam, grandParentVariable, parentVariable, childvariable);
The engine starts looking for the 4 referenced variables.
It firsts looks in innermost scope that is Scope 4 of child
function. So, within this scope, it will only see var childvariable = 50;
but there, are others still to find out.
So, Engine will continue it’s journey in quest of other variables, and go to Scope 3 of parent
function, and will see var parentVariable = 20;
. So again, 2 more variables to go.
So, Engine reaches to Scope 2, and will find other 2 variables as well which are grandParentParam, grandParentVariable
.
Scope look-up stops after it finds the first match.
Had, there been any other variable which was not present on Scope 2, then Engine will go to Scope 1 i.e global scope to search the variable and if not found will throw Uncaught ReferenceError: globalVariable is not defined
. Meaning you are trying to access something which is not present in any of the scope, which is not defined anywhere in the code.
Better with Analogy
If you relate this with general life.
A child can have the DNA or any genetic-disease from its parent or grand-parents or any of its ancestors but not the other way round. Similarly, a nested child scope can access variable from its outer/outward parent’s scope but that scope cannot access child’s property because the look-up goes from inward to outward until the first match.
No matter where the function is invoked from, or even how it is invoked, its lexical scope is only defined by where the function was declared.
From above code, you can analyze, Anytime a function executes a scope is declared, making Javascript a function-based scope, but that’s not entirely true.
Introduction of let
and const
in ES6 open up a possibility of block scope as well, which helps us with Collision of variables, Module Management and also helps to implement Least Exposure design principle.
GOTCHA!!!!
This lexical scope can also be tricked or cheated by using 2 mechanisms in JavaScript which is considered to be a bad practice in community and is totally frowned upon.
Using eval()
and with
(now deprecated) you can modify lexical scope at runtime which leads to poorer performance. So, you also need to avoid using them.
I hope you guys will like this article. The next article would be on Closures which justifies the scope in much better way and explains all the technicality of it, until then you can browse through my previous articles.