Skip to main content
X

Explore your training options in 10 minutes

JavaScript Tutorials

Web Fundamentals: Scope and JavaScript Closure – a Primer

Christina Kopecky - December 29, 2020


One of the biggest buzzwords in the JavaScript language is closure. It’s the subject of many job interview questions at FAANG companies. In this article, we’ll talk about closure and scope, illustrate its concepts with simple examples, and then finish out with a sample question from an interview with one of the bigger tech giants.

Scope

When someone tells you something is or isn’t in scope of a project, what does that mean?

I’d like to think of a periscope or a telescope when I think of the answer to this. These instruments show us all sorts of things within the confines of the lens it has:  it’s in scope . If it’s outside the scope , you can’t see past the diameter of the lens. And shining a light on something outside the diameter is not possible. You should be thinking about this as we talk about three very important and distinct types of scope in JavaScript: local, global and lexical.

Get offers and scholarships from top coding schools illustration

Find Your Bootcamp Match

  • Career Karma matches you with top tech bootcamps
  • Access exclusive scholarships and prep courses










By continuing you agree to our Terms of Service and Privacy Policy , and you consent to receive offers and opportunities from Career Karma by telephone, text message, and email.

Local Scope

Local Scope is the smallest of the three scopes we will be talking about today. When we declare a function, anything inside the brackets ({}) is considered to be local to the function. When the JavaScript engine reads the function it will declare the variables; when it ends it will destroy the variables.

function greeting() {
 var websiteName = 'Career Karma';
 return `Hello ${websiteName}`;
}
 
console.log(greeting()); // Hello Career Karma
console.log(websiteName); // ReferenceError: websiteName is not defined

As you can see, when we “console.log()” the outcome of the invoked greeting function, we are able to access the websiteName after the function was executed. This gives us the ‘Hello Career Karma’ string that we were looking for. The console.log() of the variable that was declared inside the function throws an error because it’s not defined.

As mentioned already, the reason why websiteName is undefined is because variables are created inside functions when they are invoked and then destroyed when the terminal statement runs. Anything outside of the function does not have access to stuff inside the function unless it has a special setup.

Global Scope

This next scope is pretty much a literal translation of the phrase. A global scope takes the items declared outside of a function and reserves them in a space where all the scripts and methods and functions can access and use them for their logic.

 
let counter = 0; // global -- declared outside function
 
const add = () => { // function declaration
   let counter = 0; // local -- declared inside function
   counter += 1; 
   // counter increased by 1 -- which counter variable increased?
   return counter;
}
 
add(); // invoke
add(); //  three
add(); //  times
console.log(counter) // is this 3 or 0? Why? 

What does the code above do if we console.log() the counter at the end of the code? What do you expect to happen?

Let’s walk through the code:

  1. Counter variable declared and initiated in the global environment.
  2. Add function declared in the global environment.
  3. Add is invoked.
  4. Counter variable declared and initiated in the local environment.
  5. The local counter increases by 1 ⇐ why local and not global?
  6. Counter is returned. Function terminates.
  7. Add is invoked again
  8. Walk through steps 4 to 6 again.
  9. Repeat steps 3 to 6 again.
  10. console.log(counter) ; ⇐ What is returned?

Because the function terminates when the counter is at 1 every time, our local counter variable is redeclared and re-initiated at 0 every time the function runs. No matter what happens, the counter will always stop at 1 on the local level.

If a function finds a variable within its scope, it doesn’t look to the global scope for the variable – so the global variable never changes. So, our console.log() will output 0 as our closest defined variable within that statement’s environment is in the global environment.

Lexical Scope

The lexical scope is one of the most fundamental concepts in JavaScript. It’s the idea that the creation of a function or of a variable will be accessible to certain parts of the code and then inaccessible to other parts of the code. It all depends on where the declaration of each variable and function is.

Let’s take a look at this block of code:

const init = () => { // <== This is our outer function
 const var1 = 'Career'; // outer scope
 const second = () => { // <== This is our inner function
   const var2 = 'Karma'; // inner scope
   console.log(var1); // Career
   console.log(var2); // Karma
   return var1 + " " + var2;
 };
 
 // console.log(var2); // undefined
 
 
 return second();
};
init();
 

Here we have a set of nested functions. The init() function declares a variable called var1, declares a function called second and invokes second() .

Venus, a software engineer at Rockbot

"Career Karma entered my life when I needed it most and quickly helped me match with a bootcamp. Two months after graduating, I found my dream job that aligned with my values and goals in life!"

Venus, Software Engineer at Rockbot

When the compiler passes through this code the first time, it takes a high level look at what we have:

  1. init() function
  2. invoke init()

At this point, we can’t see anything else inside the init() function – we just know the function exists. When our init() func is invoked, the compiler takes another high level look at what’s inside the function:

  1. var1
  2. second() function
  3. invoke second()

The init() function knows nothing about what is going on inside the second() block. It can only see what is in its lexical environment – its surrounding state.

Each nested function is in a smaller container, like a set of those Russian matryoshka nesting dolls (see top of page for example if you’re unsure of what they are). The dolls only know about what’s going on inside their container and what’s already occurred or declared/read in the parent. The largest doll for example only knows that the next doll in its container exists. It doesn’t know about any of the other dolls in the set, just what’s in its lexical environment (its state) and what’s already happened (the outer scope).

In essence, we know two things:

  1. The outer scope can’t see the inner scope.
  2. The inner scope has access to the outer scope.

Because the outer cope can’t see what’s going on in the inner scope, we can safely say that this is a one-way relationship. Inner can see and use variables from the outer scope, but outer can’t see inner. This is called lexical scope .

The beauty of lexical scoping is that the value of a variable is determined by its placement in the code. The functions look for the meaning of a variable inside it’s local environment first – if it can’t find it, it moves to the function that defined that function. If it can’t find it there, it moves up the chain to the next defined function.

This becomes a very important concept in JavaScript that will come up time and again as you learn more about JavaScript frameworks and how they work. You can pass down from the outer, but you can never pass “up” in the other direction. This is very important as we get to the major topic at hand: closure .

Closure

Closure’s definition is very similar to that of lexical scope. The main difference between the two is that closure is a higher order function and lexical scoping is not. A higher order function has one basic characteristic: it either returns a function or uses a function as a parameter.

Closure is a function that can access its lexical scope, even when that function is being invoked later.

Both closure and lexical scope have their own variables, can access a parent function’s variables and parameters, and can use global variables. Let’s walk through the following code:

function greeting() { //outer scope (parent function)
 const userName = "CrrKrma1952"; // parent variable
 function welcomeGreeting() { // inner function
   console.log("Hello, " + userName); // accesses parent var
   return "Hello, " + userName; // terminal statement
 }
 return welcomeGreeting; // returns a function (which makes it HOF)
} // end of greeting()
 
const greetUser = greeting(); //
greetUser(); //  Hello, CrrKrma1952
  1. greeting() function exists, but we don’t know the contents yet.
  2. greetUser exists, but don’t know contents yet
  3. greetUser() – this invokes the previous line, which, in turn, invokes the greeting() function.
  4. userName declared
  5. welcomeGreeting() exists, but don’t know contents yet
  6. Return statement below the welcomeGreeting() block returns that very same function
  7. console.log(‘Hello, ‘ + userName) ; Our console.log here can access the parent scope to get the value of userName
  8. Terminal statement that ends the function and destroys the meaning of the variables inside the code block.

In this code, we are passing around information by nesting functions together so that the parent scope can be accessed later.



Conclusion

In this article, we talked about a pretty hefty JavaScript subject: Scope and Closures. I would recommend branching out and reading several different articles on the subject. The way this is taught can come from a variety of points-of-view – which means, of course, that there’s lots of ways to learn it. I hope this primer was helpful to you! Good luck in continuing your study on closures!

About us: Career Karma is a platform designed to help job seekers find, research, and connect with job training programs to advance their careers. Learn about the CK publication.

What's Next?

Christina Kopecky

About the author: Christina is an experienced technical writer, covering topics as diverse as Java, SQL, Python, and web development. She earned her Master of Music in flute performance from the University of Kansas and a bachelor's degree in music with minors in French and mass communication from Southeast Missouri State. Prior to joining the Career Karma team in June 2020, Christina was a teaching assistant, team lead, and section lead at Lambda School, where she led student groups, performed code and project reviews, and debugged problems for students. Christina's technical content is featured frequently in publications like Codecademy, Repl.it, and Educative.

Skip to main content