Event Loop
The JavaScript interpreter is single-threaded. It handles call-backs, timers, and other asynchronous operations using an event loop. Once a page is loaded, and JavaScript has executed any inital code sections, it enters the event loop. The loop uses a FIFO-organized task list, and list of active timers. The event loop behaves something like this:
while true do for each timer x whose time has come (or past, but hasn't yet fired) do push x's task on the back of the task list if x does not recur (not an interval) then remove x from the timer list endif done while the task list is not empty do t ← pop front of task list call t done sleep until the first of: some external event queues a task on the list the firing time of the earliest timer on the timer list done

An “external event” includes things like mouse clicks or keyboard events which have on handlers attached. It also includes relevant network or disk events.

Note that when a task t is run, the event loop is just waiting for it to return; in particular, it is not running other tasks. That means that no event handler can be interrupted while it is running. There is no need for locks or synchronization as in a threaded app. The execution of t may add tasks to the queue, or external events may add some while t is running, but they but they won't be started until after t completes. So, if you try to see how fast you can click a button, the handler calls will never overlap. You'll just make the queue longer. This allows JavaScript to provide a form of asynchronous programming which is not general threading, but has the advantage of not needing explicit locks and waits.

It also means that, while t is running, any other event (asynchronous or timed) is delayed until afterwards. You need to be sure none of you handlers takes a long time, since that will freeze the browser window. This is one of the reasons not to use alert, since it won't return until you dismiss the window.

You can also see how timed events may be delayed. Any setTimeout or setInterval execution adds a timer to the list, and if nothing else is going on, the wait will end just in time to run it. But if things are busy, or if several events are secheduled at once, the loop may be running some other t, and won't get around to the timed event until it's a bit late.

As noted, the event list must have FIFO behavior for the interpreter to operate correctly. Some sort of linked list would seem the obvious choice. The timer list is essentially a priority queue, since you add timers in the order the code requires, and remove them in ascending time order, so a heap would seem to be the obvious choice. But the correctness of the system depends on its behavior, not its data structures.