Programming interactive systems
HCI1102 · 15 credits · Level 4
Programming from first principles with events, state and the interface as the target, in Python and JavaScript.
Ten units
- 1What makes programming hard is the model, not the syntaxNovice failure is caused by an incomplete mental model of the machine and of how programs compose, not by syntax, so instruction that front-loads language features teaches around the difficulty rather than to it.
- 2Names, references and the cost of representationThe choice of data representation fixes both what a program means and what it costs, and the errors beginners make with assignment and aliasing are errors of the machine model rather than of the language.
- 3An interactive program is reactions, not a sequence: the event loopBecause 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.
- 4The document is a tree, and the pixels are not the interfaceThe document object model is a logical tree from which the browser derives the trees a person actually receives, including the accessibility tree, so a control that exists in markup can be absent from the interface a user gets.
- 5Keeping state and view apartSeparating application state from its rendering removes the class of bug in which two copies of the same fact disagree, but it relocates rather than removes the risk of untested logic, so the separation is a dial and not a rule.
- 6The network is a second machine: failure is a state to designA program that depends on a remote service must treat unreachability, refusal and slowness as distinct first-class states, because an interface can only act on a failure it has modelled.
- 7Debugging is comprehension, and the tool is not the skillFinding a fault is a comprehension problem, and the gap between the debugger's reputation and its measured use shows that the availability of a tool does not supply the skill to interrogate a program.
- 8Tests make change safe; the evidence on writing them first is contestedAutomated tests are what make change safe, but the claim that the order of writing them causes quality is not supported once step size, discipline and context are controlled, so the defensible rule is tests throughout in small steps.
- 9Version control records decisions and coordinates peopleVersion control is not storage but a record of decisions and a coordination mechanism whose costs fall on people, so branches, merges and reviews are judged by the coordination they enable and the conflicts they create.
- 10From building to modelling interactionOnce an interface can be built, the remaining questions are models of movement and task, and laws such as Fitts's law and models such as the keystroke-level model are predictive enough to constrain design while resting on assumptions that limit where they hold.
10 of 10 units written. Rows marked in preparation have no body yet.
Specification
| Assessment | Weight | Type |
|---|---|---|
| Portfolio of three interactive programs | 40% | coursework |
| API-driven application with tests | 40% | coursework |
| Code reading examination | 20% | examination |
What you will be able to do
- Write correct programs using functions, collections, classes and modules in Python and JavaScript.
- Explain the event loop and write event-driven code that does not block the interface.
- Separate application state from rendering and say why the separation matters.
- Use a debugger, unit tests and version control as ordinary working practice.
- Build a small interactive application that calls a public API and handles its failure.
The reading this module rests on
- Haverbeke, M. (2018). Eloquent JavaScript. 3rd edition. San Francisco: No Starch Press. Free online, and the best explanation of the language's awkward parts.
- Downey, A. Think Python: How to Think Like a Computer Scientist. O'Reilly. A gentle but serious introduction to programming in Python.
- MDN Web Docs. Mozilla Foundation. The reference students will use for the rest of their careers.
- Chacon, S. and Straub, B. Pro Git. Apress. Version control taught properly, and free to read.
Programming interactive systems
A program is not a list of syntax. It is a model written in a language a machine can run. When the thing modelled is an interface, the model gains a second job: it must change while a person uses it, and show that change. So the program holds two things at once: what it knows and what it draws. A program that computes a report can stop. One that drives an interface never does.
Two languages carry the work here. Python handles logic and data: reading a file, filtering a list, shaping a result. JavaScript handles the interface: the controls, their events and the pixels that change in response. Eloquent JavaScript treats the second as a language worth studying on its own terms (Haverbeke, 2024). Read them side by side, and one language's habit stops looking like a law of programming.
The program is a model of state, not a list of steps
The model must change while the program runs, so its smallest unit is a value with a name. A variable is a name, not a box in the machine.
A function names one decision, so you can test that decision on its own.
The choice of collection has a measurable cost. Think Python gives two functions one task. In a list of 113,783 words, each function must find every word whose reverse is also a word. The exercise assumes an alphabetic script and an English word list, so it is language-bound. The first checks each reversed word against the list. Downey reports it runs for more than a minute. Membership scans from the front, and the loop makes roughly 13 billion comparisons. The second function uses a dictionary. He reports under one hundredth of a second, about 10,000 times faster (Downey, 2024). A dictionary is a hash table, so a lookup costs about the same however many items it holds. The timings are his, on his machine, for one task.
Ownership of a mutable value is the second decision. Downey sets b = a, then b[0] = 5, and a then reads back [5, 2, 3] (Downey, 2024). Passing a list to a function adds one more name for it, so a function that pops the first element also changes the caller's list.
One thread runs your code to completion, so a slow job freezes the page
An event is what changes the model. A click or a key press does not call your code at a random moment. The HTML Standard defines the machinery: an event loop has one or more task queues (WHATWG, 2026b). MDN (Mozilla's web documentation) presents the same model as a heap, a stack and a queue of jobs on one thread. Each job runs to completion before the next begins.
Code that must deliver a later result uses a callback, a promise, or await. Eloquent JavaScript takes those three in turn (Haverbeke, 2024). Its callback example reads one file, then a second inside the first's callback, so the indentation grows with each step. The promise version returns before its work is done, letting the caller chain the next step.
MDN states a rule a student can test: a job that takes too long stops the page processing clicks or scrolling. Worked case: a button handler runs a loop for two seconds. A mouse click and a key press queue alike, so while the loop runs there is no paint, no click and no keystroke. If you move the loop into a worker, the interface answers again. The page creates the worker with new Worker("worker.js") and passes work by message, because a worker cannot touch the page. The work is no faster. The thread that answers the user is free.
A frozen thread is not the only way an event goes missing. A target too small for a hand with reduced fine control yields none, and no code can supply one.
The document is a tree, and the tree is not the state
The person sees a second model, and it is easy to mistake it for the state. The DOM (document object model) is the browser's representation of the page as a logical tree. Each branch ends in a node, and each node holds objects (Mozilla Foundation, 2026). From that tree the browser builds a second one, the accessibility tree, which a screen reader reads instead of the pixels. A control drawn only as a shape can sit in the DOM and be missing from the tree.
Do not treat the tree as the truth. One field shows why. MDN's reference describes the value property as the input's current value, and the value attribute as its initial value. The method getAttribute returns the attribute. A search box marked up with value="draft" therefore hands "draft" back to any code that reads that attribute. The two agree until the field is touched.
Worked case: a filter box above a list. Suppose the filter text lives only in the input node. The list must reach into the DOM for it, and a re-render can wipe it. Suppose instead that the text lives in a state variable. The input then renders from that value, and the list filters from the same value. That same value can fill the label element, so the visible label and the accessible name (what a screen reader announces) come from one place. There is now one thing to be right or wrong.
Keeping state and view separate prevents a class of bug
State is what the application knows. The view is what the person sees. Once the two drift apart, every bug becomes a question of which copy is correct.
Feathers made the argument in 2002 on a dialog that lists audio filters in an ordered chain. He writes the smart object first, a class named ChainComposer, and drives it from tests against a stand-in view (Feathers, 2002). The dialog, ComposeChainDialog, comes last, and its whole job is delegation.
A tax calculator whose view can read and write the model shows the failure. The logic grows where no test can reach it. In Feathers's words, you would have to hire a lawyer to find it (Feathers, 2002). The mechanism is access, and a view handed values cannot grow logic.
The strongest counter is that separation moves the risk rather than removing it: logic can grow in any untested class. Feathers reports no defect count either way, so what would settle it is one feature built both ways and counted. While it stands, do the part both designs require: a value computed from another should not be stored twice. Several frameworks ship a signal, a value whose dependencies are tracked automatically. TC39's proposal to add signals to the language reached Stage 1 in April 2024, with no browser support yet (TC39, 2024). Treat separation as a dial rather than a rule.
A request can fail in two ways, and a blank panel hides both
A program that keeps its results needs somewhere to put them. Reading and writing files is how work persists: Python opens a file, reads text or bytes, and parses it (Downey, 2024).
Calling an API (application programming interface) is how a program uses data it does not own. In JavaScript, fetch returns a promise for a response. Python's urllib.request reference describes the same shape: the call returns a response object whose status and body the caller can read, and a protocol error is raised rather than returned (Python Software Foundation, 2026).
The Fetch Standard separates two failures a reader may not expect to be different (WHATWG, 2026a). It defines a network error as a response of type error with status 0, which is not the same as a response carrying an error status. An offline device produces the first, and so does a connection too slow to finish, an ordinary condition for a reader on a metered link. A refused request produces the second. Worked case: a weather lookup filling a panel. The panel needs four states rather than one: loading, success, empty and error. If the code renders only success, both failures look identical. The panel shows a blank rectangle, and a status code gives the reader nothing to act on. Design the failure first.
Knowing the debugger and reaching for it are different things
A print statement shows a value at a place you chose in advance, and it changes how the program runs. A debugger stops the program at a chosen point. It lets you read the whole call stack (the chain of calls that led here) and inspect any variable. You can step one instruction at a time. A conditional breakpoint stops only when its condition is true, on the four-thousandth iteration as easily as the first.
Beller and colleagues surveyed 176 developers and instrumented 458 more with a purpose-built plug-in inside Eclipse and IntelliJ. They did not run the debugger in 91 per cent of IDE sessions. They spent under 14 per cent of that time in it. Yet 81.3 per cent of the surveyed developers said they use the IDE debugger. 71.6 per cent reported using print statements. Self-report and instrumentation disagree.
The paper also records what the tool is used for: the vast majority used line breakpoints, and most had not heard of the advanced kinds (Beller et al., 2018). The study is observational, it covers two IDEs over two months, and 84.1 per cent of its survey respondents said they used Java (Beller et al., 2018). It does not show that most developers never acquire the skill.
Tests make change safe, and the order of writing them is not settled
A unit test calls one small part of a program and checks what it returns. A debugger answers a question you have now. A test answers it again next month. Think Python carries two tools for this, doctest and unittest, and the material arrives unevenly: doctest appears at Chapter 7, unittest only at Chapter 18 (Downey, 2024).
Feathers supplies the worked instance. A test there builds a ChainComposer around a stand-in view and checks the filter the view holds, so it touches no interface at all (Feathers, 2002). Written from the first week, tests catch a regression, document what the code is for, and make a change safer.
Does writing the test first cause better software, or does something else? The literature points both ways. Nagappan and colleagues followed four industrial teams that adopted test-first, and report pre-release defect density down by between 40 and 90 per cent against comparable projects, at a cost of 15 to 35 per cent more initial development time (Nagappan et al., 2008). Those teams chose the practice with no control group, so the gain may belong to their discipline as much as to the order. Fucci and colleagues, with 82 development tasks from 39 professionals, found a different pattern: sequencing had no important influence, while fine-grained, uniform steps carried the better outcomes (Fucci et al., 2017). One study watches whole products, the other single tasks, and both measure only functional correctness. Neither is decisive, so require tests throughout and small steps, without wearing test-first as a badge.
Git records decisions, and a branch is only a pointer
Tests protect the behaviour. A commit records why the behaviour changed. Git stores a project's history as commits, each a named set of changes with a message. A branch is a movable line of work, so you can build a feature without disturbing what works. A branch is cheap because it is a file holding the 40-character SHA-1 checksum of the commit it points to (Chacon and Straub, 2014).
Worked case, from the book. Two lines of work, one named iss53, have rewritten the footer of index.html in different ways. Git cannot combine two different edits to the same lines, so it stops the merge and marks the file with conflict markers (Chacon and Straub, 2014). The markers show both versions, one after the other. Resolving that is a judgement, not a mechanical step. The book keeps a little of each side, stages the file, and commits the result. The book never measures what a conflict costs.
What building hands to the mathematics of interaction
Once you can build, the next question is how to reason precisely about what you have built. A design has a number of possible states. A pointing movement has a duration, and a display removes a certain amount of uncertainty. Those questions are mathematical, and they are where this study leaves off.