Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
What is the JS Event Loop and Call Stack?

Regular Event Loop

This shows the execution order given JavaScript's Call Stack, Event Loop, and any asynchronous APIs provided in the JS execution environment (in this example; Web APIs in a Browser environment)


Given the code

setTimeout(() => { 
  console.log('hi')
}, 1000)           

The Call Stack, Event Loop, and Web APIs have the following relationship

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

To start, everything is empty


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

It starts executing the code, and pushes that fact onto the Call Stack (here named <global>)


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | |               |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

Then the first line is executed. This pushes the function execution as the second item onto the call stack.

Note that the Call Stack is a stack; The last item pushed on is the first item popped off. Aka: Last In, First Out. (think; a stack of dishes)


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | | timeout, 1000 |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

Executing setTimeout actually calls out to code that is not part of JS. It's part of a Web API which the browser provides for us. There are a different set of APIs like this available in node.


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

setTimeout is then finished executing; it has offloaded its work to the Web API which will wait for the requested amount of time (1000ms).


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

As there are no more lines of JS to execute, the Call Stack is now empty.


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   | function   <-----timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

Once the timeout has expired, the Web API lets JS know by adding code to the Event Loop.

It doesn't push onto the Call Stack directly as that could intefere with already executing code, and you'd end up in weird situations.

The Event Loop is a Queue. The first item pushed on is the first item popped off. Aka: First In, First Out. (think; a queue for a movie)


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function        <---function     | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

Whenever the Call Stack is empty, the JS execution environment occasionally checks to see if anything is Queued in the Event Loop. If it is, the first item is moved to the Call Stack for execution.


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
>   console.log('hi') | console.log       |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

Executing the function results in console.log being called, also pushed onto the Call Stack.


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi

Once finished executing, hi is printed, and console.log is removed from the Call Stack.


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi

Finally, the function has no other commands to execute, so it too is taken off the Call Stack.

Our program has now finished execution.

End.

Starved Event Loop

Below is an example of how code running in the current Call Stack can prevent code on the Event Loop from being executed. aka; the Event Loop is starved.


Given the code

setTimeout(() => { 
  console.log('bye')
}, 2)           
someSlowFn()
console.log('hi')
        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

To start, everything is empty


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

It starts executing the code, and pushes that fact onto the Call Stack (here named <global>)


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | |               |
    console.log('bye')| setTimeout        |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

setTimeout is pushed onto the Call Stack


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | | timeout, 2    |
    console.log('bye')| setTimeout        |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

setTimeout triggers the timeout Web API


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | | timeout, 2    |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

setTimeout is then finished executing, while the Web API waits for the requested amount of time (2ms).


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | | timeout, 2    |
    console.log('bye')| someSlowFn        |              | |               |
  }, 2)               |                   |              | |               |
> someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

someSlowFn starts executing. Let's pretend this takes around 300ms to complete. For that 300ms, JS can't remove it from the Call Stack


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function   <-----timeout, 2    |
    console.log('bye')| someSlowFn        |              | |               |
  }, 2)               |                   |              | |               |
> someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

Meanwhile, the timeout has expired, so the Web API lets JS know by adding code to the Event Loop.

someSlowFn is still executing on the Call Stack, and cannot be interrupted, so the code to be executed by the timeout waits on the Event Loop for its turn.


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function     | |               |
    console.log('bye')| someSlowFn        |              | |               |
  }, 2)               |                   |              | |               |
> someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

Still waiting for someSlowFn to finish...


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function     | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
> someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

someSlowFn finally finished!


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function     | |               |
    console.log('bye')| console.log       |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
> console.log('hi')   |                   |              | |               |

The next line is executed, pushing console.log onto the Call Stack


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function     | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
> console.log('hi')   |                   |              | |               |

> hi

We see hi output on the console thanks to console.log


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   | function     | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi

Nothing left to execute, so the special <global> is popped off the Call Stack.


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function        <---function     | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi

This frees up the JS execution environment to check the Event Loop for any code which needs to be executed.


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
>   console.log('bye')| console.log       |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi

Executing the function results in console.log being called, also pushed onto the Call Stack.


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi
> bye

Once finished executing, bye is printed, and console.log is removed from the Call Stack.

Notice that by this point, it is at least 300ms after the code originally requested the setTimeout. Meaning even though we asked for it to be executed after only 2ms, we still had to wait for the Call Stack to empty before the setTimeout code on the Event Loop could be executed

Note: Even if we didn't have someSlowFn, setTimeout is clamped to 4ms as the mimimum delay allowed in some cases


        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi
> bye

Finally, there are no other commands to execute, so it too is taken off the Call Stack.

Our program has now finished execution.

End.

Note: It's also possible to starve the event loop with Promises via the "Microtask queue"

@Terk101

This comment has been minimized.

Copy link

commented Jul 9, 2018

Thank you very much

@gernotpokorny

This comment has been minimized.

Copy link

commented Jul 10, 2018

Thank you, great article.

@hongquangraem

This comment has been minimized.

Copy link

commented Jul 19, 2018

Great article, thank you very much!

@cs09g

This comment has been minimized.

Copy link

commented Aug 3, 2018

There's actually one more thing called task queue that takes tasks from web APIs when time's out until call stack's empty

@gernotpokorny

This comment has been minimized.

Copy link

commented Aug 4, 2018

Can you do the same thing with promises and with promises and settimout? That would be cool.

@MichaelTsengLZ

This comment has been minimized.

Copy link

commented Sep 28, 2018

👍 Thank you so much for your explanation. Could you list some reference link, please?

@DenisButCheR

This comment has been minimized.

Copy link

commented Oct 22, 2018

Thanks! Helped!

@tribhuvan4

This comment has been minimized.

Copy link

commented Nov 12, 2018

Thanks! Great Article

@vviikk

This comment has been minimized.

Copy link

commented Nov 15, 2018

Cited this amazing page at another great Medium article on the Event Loop. Thanks a lot!

Understanding Asynchronous JavaScript — the Event Loop Sukhjinder Arora

@HabibulHH

This comment has been minimized.

Copy link

commented Nov 26, 2018

Ohhh! Nicely represented. Thanks a lot.

@hardy12994

This comment has been minimized.

Copy link

commented Nov 27, 2018

Explained well ! Thanks !

@HakimAsa

This comment has been minimized.

Copy link

commented Dec 11, 2018

I love it 👍 -))

@madhusudhan1234

This comment has been minimized.

Copy link

commented Dec 12, 2018

Thanks a lot!

@thebleshbanz

This comment has been minimized.

Copy link

commented Dec 16, 2018

thanks for sharing You knowledge its great and helpful

@hardy12994

This comment has been minimized.

Copy link

commented Dec 28, 2018

Hey @jesstelford Nice catch ! Just have one confusion. Please clear me out. As we know Call-Stack is LIFO (last in first out). but when some simple lines of code, let say 5 times console. They will go in Call Stack directly but they are executed one by one (top to bottom). So how Call Stack is behaving like LIFO (Last in first out) ???

@jinoantony

This comment has been minimized.

Copy link

commented Jan 8, 2019

Great article, very well explained. Thanks.

@jspruance

This comment has been minimized.

Copy link

commented Jan 10, 2019

Nice article. Here's a video that explains the Javascript runtime and event loop well:
https://www.youtube.com/watch?v=4xsvn6VUTwQ

@selahattinunlu

This comment has been minimized.

Copy link

commented Jan 24, 2019

This is the best explanation I've seen ever! Thanks a lot!

@internetbird

This comment has been minimized.

Copy link

commented Feb 12, 2019

Very well explained! Thanks

@tamdc2704

This comment has been minimized.

Copy link

commented Mar 22, 2019

Nice explanation! Thanks a lot.

@rtviner

This comment has been minimized.

Copy link

commented Apr 9, 2019

These visuals are really helpful, thanks!

@kevinjie

This comment has been minimized.

Copy link

commented Apr 28, 2019

nice explanation, it is very useful, thanks

@katekozlova

This comment has been minimized.

Copy link

commented May 21, 2019

cool, thanks :)

@superhuman54

This comment has been minimized.

Copy link

commented May 27, 2019

nice :)

@AndrewSavetchuk

This comment has been minimized.

Copy link

commented Jul 28, 2019

Love these visual explanations, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.