JavaScript executes on a single thread with one call stack — a LIFO (last-in, first-out) structure tracking which function is currently running and what called it.
function a() { b(); }
function b() { c(); }
function c() { console.log("deepest"); }
a();
// stack grows: a → a,b → a,b,c → logs "deepest" → unwinds: a,b → a → empty
Each function call pushes a new stack frame; returning pops it off. Because there's only one stack, JavaScript can only ever be doing one thing at a time — there is no true parallelism for regular JS code, which is the whole reason asynchronous mechanisms (covered below) exist as a scheduling trick rather than actual concurrency.
An uncontrolled recursive call that never reaches its base case exhausts the stack:
function recurse() { return recurse(); }
recurse(); // RangeError: Maximum call stack size exceeded
This is also why one long-running synchronous function (a heavy loop, an expensive computation) blocks everything — no event handler, no timer, no rendering can happen until the stack empties back out, because the single thread is occupied the entire time.