How Interactive Programs Think

A loop that waits, decides, and shows

Advanced 13 min

In this lesson

Every program you have written so far runs top to bottom and stops. A game does not. A window does not. They keep going, waiting for you to do something.

That difference is one idea — the event loop — and once you see it, games, desktop apps, and the web frameworks from Unit 6 all turn out to have the same skeleton.

Explain it like I’m 5

An interactive program is like a host at a party: it keeps watching for something to happen, then responds, then goes back to watching.

State: what the program remembers

State is everything your program knows right now: the score, the player's position, what is in the bag, whether the game is still running.

In a script that runs once, state is barely a concept: variables get set and used. In an interactive program it is the center of everything, because the same code runs over and over and the only thing that differs between one pass and the next is the state.

Keeping it in one place (a dictionary, or later an object) is what stops interactive code turning into a tangle.

The loop: wait, decide, update, show

Every interactive program is a loop that does four things: take an input, decide what it means, update the state, show the result. Then round again.

Below is that loop with the waiting removed. The commands arrive from a list instead of a person, so it runs the same way every time. That substitution is worth noticing, because it is also how interactive programs get tested.

Example
COMMANDS = ["look", "take lamp", "score", "quit", "look"]

state = {"running": True, "score": 0, "bag": []}

for command in COMMANDS:
    if not state["running"]:
        break
    if command == "quit":
        state["running"] = False
        print("goodbye")
    elif command == "look":
        print("you are in a dim room")
    elif command.startswith("take "):
        item = command[5:]
        state["bag"].append(item)
        state["score"] += 10
        print(f"you take the {item}")
    elif command == "score":
        print("score:", state["score"])
    else:
        print("i do not understand")

print("final state:", state)
Output
you are in a dim room
you take the lamp
score: 10
goodbye
final state: {'running': False, 'score': 10, 'bag': ['lamp']}
A complete event loop. The fifth command never runs.

Terminal input versus events

There is a real difference between the loop above and a game loop, and it is about who is waiting.

input() blocks: the program stops dead until the user presses Enter. Nothing else can happen: no animation, no timers, no clock.

A game loop never blocks. It runs continuously, perhaps sixty times a second, and each pass asks whether anything happened. Nothing pressed? Fine — update and draw anyway. That is why a game keeps animating while you sit still, and why it must redraw constantly rather than once.

The structure is the same. The difference is that a game's loop keeps turning whether or not you do anything.

Write the decision half of an event loop. Walk the commands, and for each one: "north" counts a step and prints that you walked, "quit" stops the loop, and anything else prints the help line. The loop must stop processing once running is False, so the final "north" never happens.

COMMANDS = ["north", "help", "north", "quit", "north"]

state = {"running": True, "steps": 0}

for command in COMMANDS:
    if not state["running"]:
        break
    # TODO: "quit" stops the loop and prints "bye"
    # TODO: "north" adds a step and prints "you walk north"
    # TODO: anything else prints "commands: north, quit"
    pass

print("steps taken:", state["steps"])
print("still running:", state["running"])

Keep the decisions out of the loop

The example above has all its logic inside the loop, which is fine for five commands and a disaster for fifty. Interactive programs grow, and they grow in the loop.

The fix is the one this unit will apply repeatedly: separate the logic from the display. Put “what does this command do to the state?” in its own function, and leave the loop to do nothing but fetch input, call that function, and show the result.

That split is worth the effort for a reason beyond tidiness: the logic half becomes testable without a screen or a person, which is exactly why every Try-It in this unit can run here at all.

Common mistake: Expecting an interactive program to run top to bottom once

Why it happens:

Every script so far has done exactly that.

How to fix it:

Setup runs once; the loop runs forever. Anything that should happen repeatedly belongs inside it.

Common mistake: Putting all the logic inside one giant loop

Why it happens:

Adding another elif is always the quickest next step.

How to fix it:

Move each decision into a function. The loop should read as fetch, decide, show.

Common mistake: Changing the same state from several places

Why it happens:

It is convenient to update the score wherever the score changes.

How to fix it:

Keep state in one structure and change it in as few places as possible, or you will not be able to work out what changed it.

Common mistake: Forgetting a way out of the loop

Why it happens:

The exit condition is easy to leave until later.

How to fix it:

Write the quit branch first. A loop with no exit is the most common way to hang an interactive program.

What is state?

What does an event loop do?

Why does a game need to redraw repeatedly?

Mini exercise (medium)

Apply the separation this lesson argues for. Write a handle(state, command) function that takes the current state and one command and returns the message to display, changing the state as needed. Support "add <item>", "list", and "quit", with anything else returning an unknown-command message. Then drive it with a loop that does nothing but call handle and print.

Practice here. Fill in the missing piece and click Run to try your answer in place.

def handle(state, command):
    """Change the state, return the message to show."""
    # TODO: "add <item>" stores the item and reports how many there are
    # TODO: "list" reports the items, or says the bag is empty
    # TODO: "quit" stops the loop
    # TODO: anything else reports an unknown command
    return ""

state = {"running": True, "bag": []}

for command in ["list", "add rope", "add torch", "list", "dance", "quit"]:
    if not state["running"]:
        break
    print(handle(state, command))

print("final bag:", state["bag"])

What to learn next

You met the shape every interactive program shares: state as the thing the program remembers, and an event loop that fetches input, decides, updates and shows, round and round until a piece of state says stop. You also saw why input() blocks while a game loop never does.

Time to build one properly. Build a Better Number Guessing Game turns the classic first project into code you can actually keep changing.