The var, let and hoisting nuances in Javascript
What is the difference between the var
and let
in Javascript ? This is one the favorite questions for the interviewers, and at the drop of the hat, we say “Var is function scoped and let is block scoped”. If you have been doing that, then hey 👋, welcome to the club. I was doing that too, for a really really long period of time. Make no mistake, it is correct, but there is more to it and knowing these nuances is what makes you stand apart from the crowd.
var
can be redeclared, whilstlet
cannot be redeclared.
var a = 5;
var a = 6;
console.log(a); //6, This would worklet b = 1;
let b = 2; // This would throw SyntaxError
2. Var
is function scoped and let
is block scoped
var + let
usage:
In the above snippet, since we have created the variables result
and i
using var
keyword, they can be accessed and used throughout the addArray
function anywhere. But, if you see the snippet, it makes sense for result
to live throughout the function as it is being returned, but why should the variable i
take up the memory space and live as long the function execution is done ? It makes no sense for i
to live throughout the function as we need it only within the for
loop. Let’s go ahead and modify the snippet.
As let
is scoped just to its nearing enclosing parentheses, which in this case is the for
loop. As soon as the for
loop is done, i
is gone and accessing it outside the for
loop would throw a ReferenceError.
I have seen in many tutorials where the instructors give you two thumb rules.
- Use
const
where ever possible. - If it is not possible, then use
let.
But don’t usevar
.
I have been doing that too, but now I respectfully disagree with those thumb rules. Instead of completely throwing away var, I learnt from my amazing instructor Kyle on using var, let & const
stylistically in the program in such a way that it would improve the readability of the program. Your program might not look fancy with all the es6, but it sure as hell would be easy to understand and read; which in my opinion is far more important than the former. Let me explain that with a simple snippet.
I find this snippet much more intuitive as I can understand that projectDetails
which was created using var
is going to be used throughout the function, whilst projectID
which was created using let
is just needed in a line or two and I don’t have to worry about it throughout the function. This is just one example and it might sound lame to some, but I believe these small things are the ones that makes our code stand out, rather than just using let
and const
everywhere to make it fancier.
(Not everyone with resonate on this, and if you’re not on the same page, then please stick with your style)
3. var is hoisted, while let is also hoisted 😜but … (my fav part)
Yes, you read that right. People often say, var
is hoisted to the top, whilst let
is not. It is technically not correct. This is quite tricky as it is deep inside the specs.
I’ll first explain it with var.
With hoisting, the code is actually equivalent to
var a;
console.log(a);
a = 2;
console.log(a);// Javascript moves all the declarations to the top.
When you run any javascript code, it goes thru two passes.
1) Pass 1 is the compile time pass, where all your formal variable and function declarations are added to the scope during this pass
2) Pass 2 is the run time pass, where your instructions are processed.
So lets run the above snippet vocally, just like how the javascript engine does it.
Pass 1 (compile time) :
- The first formal declaration statement that our compiler encounters is
var a=2;
(forget the right hand part, consider only LHS) - The conversation between complier and global scope goes like this,
Compiler: “Hey scope of global, have you seen the identifiera
? “
Global scope: “No, I’ll create one for you”. - When compiler finishes it’s pass, we will have all our formal declarations added to their respective scopes. In our case we just have one formal declaration
var a
and just one scope (global scope). So, identifiera
is added to the global scope.
Pass 2 (runtime) :
- Before the interpreter executes our first instruction, it does something interesting since we have declared our variables using
var
keyword.
It initializes the declarations made usingvar
keyword with the valueundefined
- So after the above step, now our instructions gets executed.
console.log(a); //undefined
/* The interpreter looks for variable a and we have it right there in global scope with the value undefined. */var a = 2;
// The interpreter assigns new value to a .console.log(a); //2
/* The interpreter looks for variable a in global scope and, we have it right there with the new value 2.*/
So, variables declared with
var
keyword, gets hoisted to the top and also gets initialized withundefined
before the first execution is processed.
Bear with me a little more.
I’ll now explain the same hoisting with let
keyword.
Pass 1 (compile time) :
- The first formal declaration statement that our compiler encounters is
let a=2;
- The conversation between complier and global scope goes like this,
Compiler: “Hey scope of global, have you seen the identifiera
? “
Global scope: “No, but let me go ahead and create one for you”. - When compiler finishes it’s pass, we will have all our formal declarations added to their respective scopes. In our case we just have one formal declaration
let a
and just one scope (global scope). So, identifiera
is added to the global scope.
Pass 2 (runtime) :
- Here our interpreter does nothing interesting, it just executes our instructions since we don’t have any formal declaration using
var.
Formal declarations made usinglet
do not get initialized. - So our interpreter straight away executes our first instruction.
console.log(a); //ReferenceError
- Our code execution stops with the first line and does not go beyond that.
So, now we can say that both var and let
gets hoisted to the top, but the difference is that,
var
gets added to the scope and it also gets initialized withundefined.
let
on the other hand gets added to the scope, but does not get initialized. It is initialized only at the presence oflet
keyword, ie, only when statementlet a = 2;
is encountered. So trying to accessa
at line 1, gave us ReferenceError.
let
andconst
are hoisted (likevar
,class
andfunction
), but there is a period between entering scope and being declared where they cannot be accessed. This period is the temporal dead zone (TDZ).
Summary:
var
can be redeclared,let
cannot be redeclared.var
is function scoped,let
is block scoped.var
is added to the scope(compile time) and initialised before interpreter proceeds with other statements,let
is added to scope (compile time) but does not get initialised at the start. It is initialised only at the presence oflet
keyword.
Kill it the next time when interviewer asks you about var
and let
😄.
Hope you enjoyed ! :)
Thank you for reading. If you find something wrong or better ways to do it, let me know in the comments below.
If you like the post, hit the 👏 button below so that others may find it useful. You can follow me on Twitter.