A photograph of the author, Alex Kharouk

Alex Kharouk

How does JS even work??

You ever feel tired about not being able to explain why JavaScript is single-threaded, why the event loop matters, and how a JavaScript engine uses a memory heap and a call stack to execute code? Me too, so let's dive in together and explore...

## How JavaScript Works

We have a program, which is a JavaScript file. A program can allocate memory for variables, creating data structures, etc. A program can also parse and execute the code. This program gets executed by a JavaScript engine.

The two well-known engines are the V8 engine and Node. The V8 engine (what Google Chrome uses) is a JavaScript engine that runs in a web browser. The Node engine is a JavaScript engine that runs on the server. Focusing on the V8 engine, it contains a memory heap, and a call stack. Like the program description, the memory heap contains all the memory allocation aspects. The call stack is where the code is run and executed.

## Memory Heap

const a = 1
const b = 2
const c = a + b

These variables need to be stored somewhere, thus the memory heap. Unfortunately, the memory heap is not magical, and you can not store an infinite amount of variables. So let's say you have thousands of these variables, soon enough you will run out of space. This is where memory leaks1 happen, and why sometimes your chrome tab starts crashing, as your other tabs follow suit. Chrome just couldn't handle yet another tab in your ever-growing list!

## Call Stack

const func() {

Our program here logs 1, 3, and then 2. The call stack works like a stack of plates2. The first log statement is added to the call stack and the popped off right after. Same with the third console log. The func() gets called (pushed to the call stack), then calls another log (pushed on top of the func() call), gets popped off, and finally func() is popped off. The key message here is that as functions call other functions, the call stack gets deeper.

## Single-threaded?!

The single thread means we have one call stack only, therefore we can only do one thing at a time. An example would be if we had two stack of plates, instead of one. We might have a cleaner for each stack, and thus we can do two things at a time. Thus, languages with multiple stacks are known to be multi-threaded.

JavaScript + being single-threaded makes it easier and more maintainable (trading off more speed/power) with just a single stack. With the ability of pushing onto the stack, and popping them off once the function is executed, that's what makes JavaScript a synchronous language. It's just the engine going down, line by line, executing and executing. Functions down the program will not be called until the previous function is finished.

There is a slight issue with being synchronous. If you have a function that takes a long time to execute, what do you think will happen to the next function? It's going to have to wait. This is called blocking, which can be quite an issue when your application is a website, or you need to wait for a network request to finish. The solution, is to be asynchronous.

We want to be able to do things in parallel, and not have to wait for the previous function to finish. It might sound impossible, since we just talked about how JavaScript is single-threaded, but thankfully, it's not. It's all thanks to the JavaScript Run-Time Environment3, and the magic of the event loop!

## Event Loops

Browsers also contain a run-time environment for JavaScript. This allows for additional features, such as Web APIs like the DOM4, setTimeout, ajax/fetch requests. But browsers also have a callback queue, as well as the infamous event loop. By the way, I love seeing that just in one little blog post, there's three data structures that we've discussed. Heaps, stacks, and queues.

### Quick demonstration

setTimeout(() => {
}, 2000)
const caller = () => {
const secondCaller = () => {

The above snippet will have us interact with the call stack -> the web api -> callback queue -> and finally the event loop. Try to guess the order, and then paste it into your browser. The order that follows:

  1. The first console.log is pushed to the call stack. It gets executed, logs 1, and then is popped off.
  2. The setTimeout function is pushed to the call stack; it's part of the Web API so it gets passed to the Web API and the callback queue. Since it's done executing (from the call stack's perspective), it is popped of the stack.
  3. We create a function called caller which is a closure around secondCaller. caller gets added to the call stack, and then console.log(4) is added to the call stack.
  4. We log 4, pop that log off, and add secondCaller to the call stack, followed by its own log of 3.
  5. 3 gets logged, which pops off the console.log, then pops off secondCaller, and finally pops off caller.
  6. Phew, we finally push, execute, & pop console.log(5)

Not too bad right? Oh, wait!

  1. The setTimeout function!

This whole time, we had a setTimeout function that will execute a log in two seconds. Once those two seconds are up, the Web API calls the callback function within the setTimeout function. Ergo, the callback queue. That's what we pass in as the timeout's first parameter: setTimeout(callback, time). That callback function is added to the callback queue. But what dequeues the callback queue?

## The event loop, for real.

The event is a lovely loop that goes on and on, checking the call stack. If it doesn't see anything in the call stack, it checks the callback queue. If it sees anything (which in our example, it will), it will dequeue, and push to the call stack. That's where it will call the callback, see the log, push the log, execute & pop the log, and then finally, pop the callback.

The event loop fuels the run-time environment within browsers. It's what allows us to use Web APIs, allow us to postpone execution, and allow us to create callbacks that will get called later, when the call stack is free, and the event loop can pick it up and bring it to the stack for execution.

### It's much more than that

There's a lot more to how JavaScript is executed. In this blog post, we mostly discussed the basic execution steps taken when JavaScript is executed within the browser. We looked at the different data structures involved with executing JavaScript. At the end of the day, discussing the process feels much longer than what is actually going on. It might not be important in your day to day, but if you're curious like me, you can't help but wonder why things work the way they do. Especially when it comes to asynchronous programming. So that's exactly where we'll go from here. The next JS-specific post will be all about async JS. Stay tuned!