An interactive program is reactions, not a sequence: the event loop
HCI1102 · Unit 3 of 10
Because a browser runs one thread to completion, an interface is a set of handlers and continuations rather than a straight line of steps, and any long computation must be moved off the thread that serves the user.
An interactive program is reactions, not a sequence
A program that computes a monthly invoice and a program that drives a form look alike in source, both lines of statements that appear to run in the order written. The resemblance ends when the second program starts. A person can click, type or scroll at any moment, and the program must answer whenever that happens, not when its author decides. This unit argues that the browser's execution model forces a different shape on an interactive program, for two reasons. A single thread carries each piece of code to completion before the next begins, so the program is a set of handlers rather than a straight line of steps, and those handlers fire in an order the writer cannot fix in advance. Any work long enough for a person to notice therefore has to move off the thread that serves the user. The strongest counter-argument, taken up below, is that moving work off the thread does not remove the ordering problem; the field's own bug studies locate faults that no change of primitive dissolves.
The claim is not that sequence disappears. The code inside a handler still runs from top to bottom; what changes is that the sequence you write is one thread of control inside an architecture of many. Mistaking the two causes a class of faults: a frozen page, an answer that arrives after the question has changed, and an ordering fault. Unit 4 asks what those handlers act on, and finds a tree rather than the pixels.
One thread runs your program to completion
The model is specified, not folklore. The HTML Standard defines an event loop with one or more task queues. A task queue is a set of tasks rather than a queue in the data-structure sense, and the microtask queue is explicitly not a task queue (WHATWG, 2026). Tasks arrive from named sources: events, parsing, callbacks and timers. The queue, not the source code, fixes the order your program runs.
MDN gives the teaching form of the same machinery: a heap for objects, a stack for calls, and a queue of jobs the event loop drains one at a time (Mozilla Foundation, 2026). Its consequence is stated plainly: a job that takes too long leaves the page unable to process a click or a scroll. Nothing is preempted: a click arriving while a handler runs is not handled halfway through, it waits.
A callback is a continuation you write by hand
Once the machine runs to completion, the rest of the program has nowhere to live. The event model gives it a place: capture the remainder as a function and hand it over. Haverbeke shows the mechanism in its least disguised form, a callback that reads one file and, inside it, reads a second (Haverbeke, 2024). Reading a name file and then a score file becomes a function whose body begins only after the first has returned. The promise version returns before its work is done and lets the caller chain the next step, and the await form lets a reader write that chain in the order it executes.
This is not a quirk of one language. PEP 492 added native coroutines with async and await to Python, defining the constructs and the event-loop-driven execution they assume (Python Software Foundation, 2015). The continuation is a general shape, not a keyword.
Decoupling in time costs a guarantee the direct call gave
What does the event model buy? Eugster and colleagues survey publish/subscribe systems and name the currency: decoupling in space, in time and in flow (Eugster et al., 2003). A producer need not know who consumes, when or how many. The browser's event loop decouples in the same way, and because handlers share one thread it trades some of that away. The publisher gains the freedom to publish without waiting, and loses the guarantee a direct call gave: that the work has happened by the next line, or at all.
In a single-threaded browser the cost surfaces as two questions the programmer must answer explicitly: when will this continue, and what happens if it never does? A design that ignores both has not removed the difficulty of ordering, it has moved it to a place the compiler cannot see.
The thread you protect is the one that answers the user
Run-to-completion turns response time into a structural property. The familiar guidance gives three limits: about 0.1 second for the sense of instantaneity, about 1 second for an uninterrupted flow of thought, and about 10 seconds for holding attention (Nielsen, 1993). Nielsen states these as design guidance and traces them to Miller (1968). The three figures are averages, not constants, so a delay that feels instant to one user is not the same for another. The attribution rewards checking, because the primary source is blunter than the summary. Miller's own review argues that delays beyond about two seconds change the character of machine communication. Ten-second responses, he adds, will not permit the continuity of thinking that problem solving needs (Miller, 1968).
The field now measures the blocking this unit warns about
The perceptual limits now have an instrument: in March 2024 the programme that publishes the Core Web Vitals replaced First Input Delay with Interaction to Next Paint, which times each click, tap or keystroke from the input, through the handlers, to the moment the browser can paint the next frame, and reports the 75th percentile of page views rather than the first interaction alone (web.dev, 2024). Its thresholds are 200 milliseconds for good responsiveness and 500 for poor, far tighter than the second at which thought is said to remain uninterrupted. The blocking this unit warns about is now measured in the field.
A handler that runs for two seconds is not merely slow. While it runs, every click, keystroke and assistive-technology request waits unhandled in the queue, so a list stops scrolling and a button stops responding. The work may be necessary, but it need not occupy the thread that answers the user. Moving it to a worker, a queue or a server leaves it just as slow and gives the interface its responsiveness back. How long the thread is held is a design decision.
Where these programs actually break
The faults follow the model. Ocariza and colleagues classified client-side JavaScript bugs: most arose from the code's interaction with the DOM and its event handling, those faults were slower to fix than the rest, and most originated in the JavaScript rather than the surrounding components (Ocariza et al., 2013). The program fails where it meets the changing page.
Across 138 JavaScript programs holding five million lines, every tenth function definition took a callback and more than half of all callbacks ran asynchronously (Gallaba, Mesbah and Beschastnikh, 2015). The callback is the ordinary way to write this code, and the construct the bugs cluster around.
The difficulty is not confined to JavaScript. Okur and colleagues analysed 1,378 open-source Windows Phone applications, twelve million lines of code from 3,376 developers. They found 14 per cent of methods using an expensive construct they did not need and 74 per cent of async methods capturing context unnecessarily, a choice that hurts performance and can deadlock (Okur et al., 2014). Their Corrector tool fixed those misuses, and developers accepted 314 of its patches.
Turcotte and colleagues identify eight anti-patterns in promise-based JavaScript and build a detector for them, finding about 2,500 static instances across twenty popular repositories (Turcotte et al., 2022). Most of that code could be removed by refactoring, so the mistakes are not a phase the language outgrows.
Contested question: is the event loop the fault, or the code written on it?
The literature agrees that asynchronous code is hard, and disagrees about where the difficulty sits. One camp holds the model sound and the burden a matter of discipline. Its evidence is that tools find the faults and developers accept the fixes (Okur et al., 2014). DrAsync found about 2,500 static instances of promise anti-patterns in twenty repositories (Turcotte et al., 2022). Both corpora are large, and both measure code rather than people. They show the faults are common and detectable, not that discipline removes them.
The rival camp holds the primitive wrong. Gallaba, Mesbah and Beschastnikh supply the practice data: across 138 JavaScript programs and five million lines, every tenth function definition takes a callback argument, and over half of all callbacks run asynchronously (Gallaba, Mesbah and Beschastnikh, 2015). Zhao and Li read that shape as a design fault, and propose thread-like abstractions with cooperative cancellation (Zhao and Li, 2021). Elliott and Hudak go deeper and earlier: in functional reactive animation the primitive is a time-varying value, so change over time is expressed directly rather than as a chain of callbacks (Elliott and Hudak, 1997). Both are designs with feasibility evidence, not comparative trials, so the camp shows the callback is one choice among several, not yet that a replacement reduces faults.
A third strand keeps the disagreement honest. Lu and colleagues examined 105 real-world concurrency bugs. About 92 per cent were triggerable by enforcing an order among no more than four memory accesses, and about 73 per cent of non-deadlock faults survived adding or changing locks (Lu et al., 2008). Ordering faults have a small common core that no change of primitive dissolves, which weakens the strong version of the model-should-change position.
What would settle the question is a comparison the field has not run. Take one matched interface task, implement it on the event loop and on a thread or behaviour model, and count ordering and race faults under the same testing budget rather than counting API calls. A second test would follow a tool such as Corrector or DrAsync into use, and measure whether the faults fall. Until then, keep every handler short enough to watch, keep the order of dependent work in one named place, and give each long operation a cancellation path.
Where the argument is heading
Zhao and Li make cancellation and hierarchical lifetimes first-class, addressing the part a callback hides most completely: stopping work that is no longer wanted (Zhao and Li, 2021). Turcotte and colleagues extend detection to the runtime, because the anti-pattern count rises sharply during execution (Turcotte et al., 2022). Gallaba and colleagues locate the callback at the centre of ordinary JavaScript, so a replacement has to answer for the code people already write (Gallaba, Mesbah and Beschastnikh, 2015). Elliott and Hudak's behaviour-and-event formulation outlived its paper and became the ancestor of modern reactive libraries. The studies worth watching measure faults under a change of primitive.
The sources this unit rests on
- WHATWG. (2026). HTML Standard. Living Standard. https://html.spec.whatwg.org/multipage/webappapis.html#event-loops Specifies the event loop and its task queues and names the task sources. The normative definition of the model taught here.
- Mozilla Foundation. (2026). JavaScript execution model. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model Describes the model as a heap, a stack and a queue of jobs, and states that a long job leaves the page unable to handle a click. The teaching form and the blocking claim.
- Haverbeke, M. (2024). Eloquent JavaScript: A Modern Introduction to Programming, 4th edition. San Francisco: No Starch Press. https://eloquentjavascript.net/ Teaches asynchronous programming through callbacks, promises and await, nesting two file reads in a callback. Carries the continuation mechanics.
- Eugster, P. T., Felber, P. A., Guerraoui, R. and Kermarrec, A.-M. (2003). The many faces of publish/subscribe. ACM Computing Surveys 35(2). doi:10.1145/857076.857078 Surveys publish/subscribe and names its decoupling in space, time and flow, with the delivery costs that follow. Explains what an event architecture charges.
- Okur, S., Hartveld, D. L., Dig, D. and van Deursen, A. (2014). A study and toolkit for asynchronous programming in C#. ICSE 14. doi:10.1145/2568225.2568309 Analysed 1,378 Windows Phone apps and found 74 per cent of async methods captured context unnecessarily. The difficulty is not specific to JavaScript.
- Ocariza, F., Bajaj, K., Pattabiraman, K. and Mesbah, A. (2013). An empirical study of client-side JavaScript bugs. ESEM 13. doi:10.1109/ESEM.2013.18 Classified client-side JavaScript bugs: most arose from the DOM and event handling and took longer to fix. Grounds where interactive programs break.
- Gallaba, K., Mesbah, A. and Beschastnikh, I. (2015). Don't call us, we'll call you: characterizing callbacks in JavaScript. ESEM 15. doi:10.1109/ESEM.2015.7321196 Characterised callbacks across 138 programs and five million lines: every tenth function definition took one, and over half ran asynchronously. The callback is the ordinary construct.
- Elliott, C. and Hudak, P. (1997). Functional reactive animation. ICFP 97. doi:10.1145/258948.258973 Introduces functional reactive animation, where a time-varying value rather than a callback is the primitive. The alternative model.
- Miller, R. B. (1968). Response time in man-computer conversational transactions. AFIPS Fall Joint Computer Conference. doi:10.1145/1476589.1476628 Argues delays beyond about two seconds change the character of machine communication, and that ten-second responses prevent sustained problem solving. The primary response-time source.
- Python Software Foundation. (2015). PEP 492: Coroutines with async and await syntax. https://peps.python.org/pep-0492/ Adds native coroutines with async and await to Python. The continuation model is not JavaScript-specific.
- Nielsen, J. (1993). Response times: the three important limits. In Usability Engineering. San Francisco: Morgan Kaufmann. https://www.nngroup.com/articles/response-times-3-important-limits/ States the limits of 0.1, 1 and 10 seconds as design guidance, tracing them to Miller. The compact form of those numbers.
- Lu, S., Park, S., Seo, E. and Zhou, Y. (2008). Learning from mistakes: a comprehensive study on real world concurrency bug characteristics. ASPLOS 08. doi:10.1145/1346281.1346323 Examined 105 concurrency bugs: about 92 per cent needed an order among four memory accesses, and most non-deadlock faults survived added locks.
- Turcotte, A., Shah, M. D., Aldrich, M. W. and Tip, F. (2022). DrAsync: identifying and visualizing anti-patterns in asynchronous JavaScript. ICSE 22. doi:10.1145/3510003.3510097 Built a detector for eight promise anti-patterns and found about 2,500 static instances in twenty repositories. The mistakes persist in used code.
- web.dev. (2024). Interaction to Next Paint (INP). Google. https://web.dev/articles/inp Records the March 2024 replacement of First Input Delay with Interaction to Next Paint, which times an interaction to the next painted frame and reports the 75th percentile of page views, with a good threshold of 200 milliseconds. The field instrument for blocking.
- Zhao, T. and Li, Y. (2021). A concurrency model for JavaScript with cooperative cancellation. SLE 21. doi:10.1145/3486608.3486911 Proposes thread-like abstractions with cooperative cancellation, arguing the event model confuses order, races and termination. The primitive should change.