(V8 developer here.) You are correct, local variables in one function that another function closes over can't easily be stored on the stack. Specifically, in V8 they are stored in a so-called "Context" object on the heap, which is what @JonasWilms is referring to. To illustrate with an example:
function outer() {
let a = 1; // Will be on the stack
let b = 2; // Will be in the context on the heap
return function inner() {
return b;
}
}
You are also absolutely correct that this is not something you need to worry about :-)
In particular, because "stack" and "heap" really are internal implementation details; they just so happen to be the most common way how engines (for many languages) achieve the required behavior with decent performance.
More conceptually speaking, we're talking about scopes and lifetimes of variables here: in the example, a
goes out of scope (=becomes unreachable) when outer
finishes running, whereas b
is still reachable via inner
, so it lives on as long as inner
could be called again. How an engine accomplishes that is an internal detail and could change at any time. For example, an engine could decide to simplify its implementation by always putting all variables on the heap, avoiding the need to analyze in advance which variables will be closed over and which won't. All that matters is the observable behavior, not how it's achieved under the hood.