The JavaScript Event Loop: Explained

Posted on by in Development, JavaScript

What’s this post about?

With JavaScript approaching near-ubiquity as the scripting language of the web browser, it benefits you to have a basic understanding of its event-driven interaction model and how it differs from the request-response model typically found in languages like Ruby, Python, and Java. In this post, I’ll explain some core concepts of the JavaScript concurrency model, including its event loop and message queue in hopes of improving your understanding of a language you’re probably already writing but perhaps don’t fully understand.

Who is this post for?

This post is aimed at web developers who are working with (or planning to work with) JavaScript in either the client or the server. If you’re already well-versed in event loops then much of this article will be familiar to you. For those of you who aren’t, I hope to provide you with a basic understanding such that you can better reason about the code you’re reading and writing day-to-day.

Non-blocking I/O

In JavaScript, almost all I/O is non-blocking. This includes HTTP requests, database operations and disk reads and writes; the single thread of execution asks the runtime to perform an operation, providing a callback function and then moves on to do something else. When the operation has been completed, a message is enqueued along with the provided callback function. At some point in the future, the message is dequeued and the callback fired.

While this interaction model may be familiar for developers already accustomed to working with user interfaces – where events like “mousedown,” and “click” could be triggered at any time – it’s dissimilar to the synchronous, request-response model typically found in server-side applications.

Let’s compare two bits of code that make HTTP requests to and output the response to console. First, Ruby, with Faraday:

The execution path is easy to follow:

  1. The get method is executed and the thread of execution waits until a response is received
  2. The response is received from Google and returned to the caller where it’s stored in a variable
  3. The value of the variable (in this case, our response) is output to the console
  4. The value “Done!” is output to the console

Let’s do the same in JavaScript with Node.js and the Request library:

A slightly different look, and very different behavior:

  1. The request function is executed, passing an anonymous function as a callback to execute when a response is available sometime in the future.
  2. “Done!” is immediately output to the console
  3. Sometime in the future, the response comes back and our callback is executed, outputting its body to the console

The Event Loop

The decoupling of the caller from the response allows for the JavaScript runtime to do other things while waiting for your asynchronous operation to complete and their callbacks to fire. But where in memory do these callbacks live – and in what order are they executed? What causes them to be called?

JavaScript runtimes contain a message queue which stores a list of messages to be processed and their associated callback functions. These messages are queued in response to external events (such as a mouse being clicked or receiving the response to an HTTP request) given a callback function has been provided. If, for example a user were to click a button and no callback function was provided – no message would have been enqueued.

In a loop, the queue is polled for the next message (each poll referred to as a “tick”) and when a message is encountered, the callback for that message is executed.

Theoretical Model

The calling of this callback function serves as the initial frame in the call stack, and due to JavaScript being single-threaded, further message polling and processing is halted pending the return of all calls on the stack. Subsequent (synchronous) function calls add new call frames to the stack (for example, function init calls function changeColor).

In this example, a message (and callback, changeColor) is enqueued when the user clicks on the ‘foo’ element and an the “onclick” event fires. When the message is dequeued, its callback function changeColor is called. When changeColor returns (or an error is thrown), the event loop continues. As long as function changeColor exists, specified as the onclick callback for the ‘foo’ element, subsequent clicks on the element will cause more messages (and associated callback changeColor) to become enqueued.

Queuing Additional Messages

If a function called in your code is asynchronous (like setTimeout), the provided callback will ultimately be executed as part of a different queued message, on some future tick of the event loop. For example:

Due to the non-blocking nature of setTimeout, its callback will fire at least 0 milliseconds in the future and is not processed as part of this message. In this example, setTimeout is invoked, passing a callback function g and a timeout of 0 milliseconds. When the specified time elapses (in this case, almost instantly) a separate message will be enqueued containing g as its callback function. The resulting console activity would look like: “foo”, “baz”, “blix” and then on the next tick of the event loop: “bar”. If in the same call frame two calls are made to setTimeout – passing the same value for a second argument – their callbacks will be queued in the order of invocation.

Web Workers

Using Web Workers enables you to offload an expensive operation to a separate thread of execution, freeing up the main thread to do other things. The worker includes a separate message queue, event loop, and memory space independent from the original thread that instantiated it. Communication between the worker and the main thread is done via message passing, which looks very much like the traditional, evented code-examples we’ve already seen.

Web Workers

First, our worker:

Then, the main chunk of code that lives in a script-tag in our HTML:

In this example, the main thread spawns a worker and registers the logResult callback function to the its “message” event. In the worker, the reportResult function is registered to its own “message” event. When the worker thread receives the message from the main thread, the worker enqueues a message and corresponding reportResult callback. When dequeued, a message is posted back to the main thread where a new message is enqueued (along with the logResult callback). In this way the developer can delegate CPU-intensive operations to a separate thread, freeing the main thread up to continue processing messages and handling events.

A Note on Closures

JavaScript’s support for closures allow you to register callbacks that, when executed, maintain access to the environment in which they were created even though the execution of the callback creates a new call stack entirely. This is particularly of interest knowing that our callbacks are called as part of a different message than the one in which they were created. Consider the following example:

In this example, the changeHeaderDeferred function is executed which includes variable header. The function setTimeout is invoked, which causes a message (plus the changeHeader callback) to be added to the message queue approximately 100 milliseconds in the future. The changeHeaderDeferred function then returns false, ending the processing of the first message – but the header variable is still referenced via a closure and is not garbage collected. When the second message is processed (the changeHeader function) it maintains access to the header variable declared in the outer function’s scope. Once the second message (the changeHeader function) is processed, the header variable can be garbage collected.


JavaScript’s event-driven interaction model differs from the request-response model many programmers are accustomed to – but as you can see, it’s not rocket science. With a simple message queue and event loop, JavaScript enables a developer to build their system around a collection of asynchronously-fired callbacks, freeing the runtime to handle concurrent operations while waiting on external events to happen. However, this is but one approach to concurrency. In the second part of this article I’ll compare JavaScript’s concurrency model with those found in MRI Ruby (with threads and the GIL), EventMachine (Ruby), and Java (threads).

Additional Reading



  Comments: 59

  1. Good stuff, thanks for the write up.

  2. Hi and thanks for great explanation. Just a little typo I’ve found: “the provided callback will ultimately will be executed”, too much ‘will’ 🙂

  3. Thanks, you successfully nailed it! I like the graphics (seriously).

  4. If Javascript had proper threading we would not need asynchronous handlers. Async handlers at first seem very good but it easily devolves into a terrible stew of race conditions, continuations and various ‘continuation / promise’ frameworks. All of these problems would evaporate with a proper threading model.

    Javascript broken-ness is most evident here. The mental model the programmer has is synchrony, and synchrony is best implemented with threads and mutexes.

    • Hi kryptogoloc. You said, “The mental model the programmer has is synchrony.” Which programmers are you referring to? All of us?

    • Richard Klancer

      “If Javascript had proper threading.”… I’m sorry, but I and many others could hardly disagree more strongly.

      Javascript’s (de facto, if not de jure) single-threaded nature provides an absolutely essential invariant: between the beginning of my callback and the end, nothing else can mess with my state. See :

      “You can go ahead and leave things in a funky intermediate state for as long as you like, and as long as you stitch everything back up in time for the next spin of the event loop, no other code can run in the meantime. That means you can be sure that while your object is lying in pieces on the floor, nobody else can poke at it before you put it back together again.”

      Certain types of race conditions are possible of course–you might depend on some item of state in callback 2, forgetting that callbacks between callback 1 and callback 2 might affect it. But I believe these indicate fundamental errors in your code’s logic and/or modularity, rather than being something that happens because you forgot to be sufficiently paranoid, at every single line of code in your program, about who else might swoop in and modify shared memory.

      In Javascript implementations, you never have to worry about this kind of race condition: “Wait! What if a context switch happens *right here*, after I decrement this but before I got around to incrementing that. Let’s see, does that operator translate to a single machine instruction, or…”

      Certainly my experience is that, once the *correct* mental model establishes itself firmly in your mind, it starts to feel not only easy, but somehow essentially right, to use callbacks bound to a closure in order to capture state about one particular ongoing event in the world (a particular sequence of clicks, an http request and response, intermediate results coming in from a web worker) while having the luxury of ignoring what might or might not happen in the rest of the world. Closures-with-callbacks looks more complex at first than code that blocks on I/O to “look sync”, but it makes for code that’s easy to reason about, and in which you don’t have to constantly defend against potential failures of transactionality.

  5. Note that the JavaScript language (ECMAScript) does not define any U/I mechanisms. Rather, it’s the host environment that provides these. Hence, the event loop is provided by web browsers (and other environments like Node), so a better name would be “the browser’s event loop”.

    • Hi Šime.

      Since we’re getting all pedantic: JavaScript (the language) is an implementation of ECMA-262 (a specification). If you’re saying that an event loop is not part of the ECMA-262 specification – I agree with you. However, all modern JavaScript engines (that I’m aware of) provide some sort of an event loop. I don’t think it’s of practical value to the reader that I make that distinction up front.

      • But is the event loop built-in into JavaScript engines, in practice? Or is it provided as a separate component? If the latter case is true, then “JavaScript event loop” may not be the optimal name for this. I don’t think that discussing optimal names for these techs is pedantism, and neither should you.

  6. Richard Klancer

    Have you thought about writing up something about the HTML5 Working Group’s standardization of the (browser) event loop, and the formalization of distinct “task sources” for DOM manipulation, user interaction, networking, etc?

    (I have not read up on this subject, yet)

    Relatedly, TC39 appears to be thinking about standardizing some event loop semantics in the Javascript standard itself:

  7. Nice introduction to asynchronous calls. I know the intention was not to bash Ruby or anything, so don’t take this comment as a rebuttal. Just so it’s clear for all the readers, Ruby can do async calls as well. The initial example uses the Faraday gem to make a synchronous, blocking HTTP call, and wait for the response. Faraday can use other gems, one of them being Typhoeus, which uses libcurl underneath. And it so happens that you can do asynchronous, non-blocking HTTP calls in Ruby with Faraday+Typhoeus as well ( And this is not the only options, there are several other ways to do async, non-blocking HTTP calls in Ruby, one other example being with Eventmachine and EM-HTTP.

    • Hey Akita,

      You’re absolutely right – there are several ways to write asynchronous code in Ruby. While some of those projects are growing in popularity these days (EventMachine, for instance), nearly all of the Rails-based applications I’ve come across lately have been doing I/O synchronously. I’m not sure why the community hasn’t yet embraced asynchronous I/O. Any thoughts? Sampling error?

      • Michael Wynholds

        I think the Ruby community is now starting to embrace async I/O, because companies are now starting to use (or trying to use) Ruby in performance intensive applications.

        Regarding the approaches mentioned here:

        1. Typheous (and other async gems)
        I love Typheous, but it’s a targeted solution. It’s not practical to force developers to find individual gems with async support for every type of I/O operation they need to do.

        2. EventMachine
        I also really like EventMachine, but I don’t think it will be the way Rubyists go async. It puts the responsibility of writing Fiber-aware code on either the app developer or else all I/O-based gem developers, and that’s not a feasible approach either. Async support has to be at the language (or at least framework) level.

        In my opinion, Rubyists are moving towards threads, and specifically using an Actor model. This is actually what Web Workers are, so there is a parallel in JavaScript.

        And I think this is the right way to go, personally.

  8. > With JavaScript approaching near-ubiquity
    > as the scripting language of the web browser,

    i am confused by statements like this,
    which i seem to be hearing quite often.

    in my understanding, javascript is the
    _only_ way to put code in a web-page,
    so it’s no surprise it’s “near-ubiquity”…

    so please, if there is some other way
    that i can run code inside a web-page,
    let me know, because i don’t really like
    javascript all that much (in fact, i dislike
    it in large part), i’m just using it because
    i thought i had to. but if i had a choice…

    well, i’d certainly consider that choice…


    • Michael Wynholds

      i’m sure there are more fringe cases, but two that come to mind immediately:
      1. flash
      2. vbscript runs in ie (at least older versions, not sure about newer)

      • no future in either of those, as you know.
        so my understanding is perfectly correct.

        plus now i know why people toss in that
        small hedge, so thank you for explaining.


        • Michael Wynholds

          well, maybe your understanding is correct. but when you say that “javascript is the _only_ way to…”, it seems like you are making a point to emphasize “only” as a matter of fact. and of course, it’s not a fact.

          i would also point out that while flash may seem on the down-swing, it’s not dead yet by any means.

          however if you’re actually interested in technologies that have a future (in your opinion), then some more come to mind:


          you can write and run coffeescript directly in the browser. it’s arguable whether you should differentiate that from JavaScript.

          asm runs native in some browsers, and is a subset of javascript.

          anyway, it sort of seems like semantics, but if someone writes a blog post and says javascript is the only choice for writing code in a browser, i guarantee they will get people telling him about all these other options.

    • I can’t speak for the author, but I read the sentence you quoted as meaning “Javascript is approaching near ubiquity because it is the scripting language of the browser”. That is to say that nearly everyone (or every programmer, anyway) has been exposed to JS to some degree.

      As for having a choice, there are tools around that let you write code in other languages and cross-compile back to JS for deployment. Obviously this doesn’t let you do anything that JS can’t do natively, but it does give you a wide selection of syntax styles. See

    • With the invent of nodejs, express, , people who are saying javascript is “the scripting language of the web browser”, I cannot but disagree.

  9. Brilliant article! thanks for this

  10. setTimeout(fn, 0) is kind of confusing because, even passing 0, it won’t invoked in the same tick. JS engines of different browsers have the minimum amount for setTimeout parameter (in Chrome it’s 4ms). So in Chrome setTimeout(fn, 0) === setTimeout(fn, 4).
    But, there’s a setImmediate(fn) that will be called when the call stack is empty but before next tick of the event loop

    • Hey Bolshchikov,

      I didn’t know about window.setImmediate! According to MDN (, it seems to be available only in IE10. Can you describe a case for its use?

      Regarding setTimeout(fn, 0): I think the API is a bit misleading. The semantic promise of setTimeout is “call this function n-milliseconds from now,” but what’s actually happening is that you’re asking the engine to enqueue a message (and thus your callback) n-milliseconds from the moment you called it. If some CPU-heavy stuff causes the current tick of the event loop to last for 10000 milliseconds, your function won’t be called for _at least_ 10000 milliseconds. Grokking this behavior requires that the programmer understand that, behind the scenes, a single-threaded event loop / queue mechanism is responsible for the running of their code.

  11. The best explanation of JS concurrency model I’ve read so far. Can you recommend any other advanced reading materials regarding this topic?

  12. In the example Queuing Additional Messages :
    if I replace the the line
    setTimeout(g, 0);
    with my custom event :
    var evt = document.createEvent(“Event”);

    then the function g() is immediately executed, it is not waiting until the next loop tick . Why ?

  13. Brilliant post. Thanks:)


    It is in point of fact a nice and useful piece of info.
    I am satisfied that you just shared this useful info
    with us. Please stay us informed like this. Thanks for sharing.

  15. Thanks for publishing this. It’s a great overview.

    I also found the article below to also be helpful when learning the different event loop operations. Specifically, setImmediate and nextTick can be confusing because, despite their names, nextTick will be executed immediately, and setImmediate will be executed at the beginning of the next time around the loop.

    (As an aside, I’ve always wanted a setImmediate key in my email, to remove a message from the inbox and put it back there tomorrow. Oh well.)

  16. What if I wanted a loop to fire every time someone clicks a button five times.. so say every five clicks of a specific button it will fire off and show a picture or what ever I want it to do?

  17. Interesting.

  18. I have two questions:

    1)What would happen(to the value in the closure) if my callback uses a closure and the message fails to execute(such as a failed database read or a failed ajax request)?

    2)Assume that a promise is created and a then handler is passed to it with a subsequent then handler added to do something synchronous to the result.Will a message be created in the queue for the subsequent then handler as well?

  19. thanks for the article , really helped me to have a deeper understanding about the event loops in the javascript

  20. Simply superb..

  21. Dmitri Kourbatsky

    Excellent article

  22. Nice, I loved it.

  23. I have seen the light! Thanks!111

  24. This is one of the best and clearest explanations I could find on JavaScript callbacks wrt the event loop that I’ve seen – thanks for your work.

    I know it’s now 2016 and this blog may not get checked but here goes…

    I get how this works in practice but what I’m still trying to get my head around is the exact mechanism by which the runtime knows that, eg. in your example, the request to should be handled in an asynchronous manner. Is it the mere presence of a function in the parameter list? In other words are asynchronous operations inferred by the runtime?

    I know that, by convention, the callback function is listed last in the parameter list but does it matter if it isn’t?

    Can two or more callbacks be listed?

    If a callback function is not actually implemented (though it is written in the params) will the calling function just get on with its job and finish silently?

    Say we pass a function B as a parameter to a function A but we don’t want fn B to be treated as a callback can we do that? In other words can we force function A to be synchronous?

    Are there any checks or warnings to see if the callback function calls the original function resulting in an infinite regression of (non-blocking) workers?

    I think at the heart of this I’m trying to understand the exact definition of a non-synchronous function from the JavaScript engine’s point of view.

    I’m quite new to JavaScript and I find the implied nature of things a bit harder to get used to. Or maybe I’m just slow…

  25. You mention above that JS asks ‘Runtime’ to call a function when someone click on a button. What do you mean by this Runtime? is this multi-threaded?

    If JS calls multiples functions, are there multiple Runtime that are running at the same time? Is there a limit

  26. Joe Gibbs Politz

    Nice post and graphics!

    I think this statement is inaccurate, based on the code snippet shown:

    “for example, function init calls function changeColor”

    changeColor is only ever called from the event loop calling the callback (changeColor) in response to an event.

    A better example of a function that init calls would be addEventListener, I believe.

  27. where’s the second part?

  28. Hi,
    recently i had a problem of payment with a Customer, a French Company.
    I tried an international debt collections Company “Inter-station”.
    After only 4 Weeks, today we have received the money back !!!
    Thank you All international staff,
    Bye bye

  29. What a wonderful article!!! it resolved my confusions about nodejs threads!

  30. Dmitri Arkhipov

    Thanks for the great post, it came in very useful for me!

  31. This footwear is available in many colors. That is one other benefit of utilizing it because the wide range is providing a solution for using it on all several types of outfits or occasions. The worth of these relies on the place you purchase them. If you are cheap fitflop sale sandals low price actually expecting to buy it from online at an affordable value, then go online to an authorized Fitflop store () to choose up your choice.|Solely a decade or so ago, it will have been unimaginable that pairing a coach with a swimsuit may either look good or be accepted by the traditionalists. The trigger has been helped, nonetheless, due to designers growing a more refined sneaker, with cleaner, smarter traces and in darker colours, utilizing high quality supplies like luxurious leather. Prada Sport’s iconic black coaching shoe experimented with patent leather-based and minimised laces to create a smooth silhouette that posed a risk to the formal shoe’s standing because the natural companion for a suit. The out-dated view of trainers being bulky, brightly colored and only for sports activities nuts is finally giving way to a wider understanding that sneaker-sporting is not only for dropouts.

  32. Rx viagra levitra pharmacy erection mg

  33. farmacia italia levitra

    Rx farmacia italia sicura levitra ricetta erettile fumo senza cialis droga disfunzione raia

  34. mg pharmacy rx viagra

    Mg oral safe jelly sildenafil viagra doctor we accept visa / mastercard needed buy rx pharmacy 100% no

  35. farmacia viagra

    Antidoti vardenafil erettile disfunzione prezzo mg rimedi rx farmacia italia alla consegna effetti del della cialis nonna 20

Your feedback