What scope is
Scope is the part of a program where a name can be used. Every function call gets its own scope, a box of variables. Code sees names in its own box and in the boxes outside it, never inside.
The outermost box is the global scope. Line 1 puts step = 1 in it. makeCounter is already there: function declarations are set up before any line runs. Lines 2 to 8 only define it; nothing inside runs yet.
Making a closure
Line 9 calls makeCounter. The call gets a fresh scope of its own. Its outer link points to the global scope, where makeCounter was written. Line 3 then puts count = 0 in the new box.
Line 4 makes a new function. Every JavaScript function remembers the scope it was created in, through a hidden link called [[Environment]]. Here that is makeCounter's box. A function plus its remembered scope is a closure.
makeCounter returns the function, and line 9 stores it in next. The call is over, but its box is not thrown away: next still leads to it, through the function, so count = 0 lives on.
Calling the closure
next() runs the inner function, and this call gets a fresh, empty scope. Its outer link is the remembered one: makeCounter's box. Scope follows where a function was written, not where it is called. That is lexical scope.
count = count + step. count is not in the inner box, so the engine follows the outer link and finds 0. step is two links out, in the global scope: 1. It stores 0 + 1 = 1 back in makeCounter's box.
return count hands back 1. The call is over and nothing refers to its box any more, so the engine may throw it away. makeCounter's box is different: next still leads to it, holding count = 1.
console.log(next()) calls the function again. A new inner box links to the same makeCounter box: count goes from 1 to 2, and the console prints 2. The value survived between calls: that is what closures are for.
Why it matters
Nothing outside can reach count. console.log(count) at the top level throws a ReferenceError, because lookup only goes outward. Each makeCounter() call makes a separate box, so two counters never share a count.
What happens
// step 1/10 · scope
scope where a name can be used
global names at the top level of the script
function every CALL gets a new box of its own
block let and const inside { } (not used here)
lookup own box first, then outward, never inward
The boxes are what the ECMAScript specification calls Environment Records. Each has an outer link ([[OuterEnv]]); a function call's record is made by NewFunctionEnvironment, and its outer link is the function's [[Environment]]. JavaScript has no command to free memory. A garbage collector removes values that can no longer be reached; when it runs is up to the engine. Engines may also keep only the variables a closure actually uses.