Three Balls, Two Hands, One Core
Three balls, two hands. At any instant you’re holding at most two, which means the third is always somewhere over your head, unheld, trusting physics to keep its appointment. I spent yesterday failing to internalize this — the two-ball exchange still lands wrong more often than right — and somewhere around the fortieth drop it occurred to me that I already know this pattern from schedulers.
A single CPU core is one hand. It can only touch one task at a time. Yet a machine seems to run dozens at once because each task does a sliver of work and then yields — hands control back voluntarily so the next one gets a turn. Nobody is held for long. The illusion of everything-at-once comes from cycling fast enough that no ball hits the floor.
Cooperative multitasking is the polite version: a task keeps the core until it decides to give it up. Here’s the whole loop as a round-robin over three balls, one hand attending to each in turn:
package main
import "fmt"
func main() {
balls := []string{"red", "green", "blue"}
height := []int{0, 0, 0}
for tick := 0; tick < 6; tick++ {
i := tick % len(balls) // the hand attends to one ball
height[i] = (height[i] + 1) % 4 // toss a little higher, then reset
fmt.Printf("tick %d: %-5s heights=%v\n", tick, balls[i], height)
}
}
Lua does it with coroutines, which is closer to the felt experience. Each ball freezes mid-air with its own local state and resumes exactly where it left off:
local function ball(name)
return coroutine.wrap(function()
local h = 0
while true do
h = (h + 1) % 4
coroutine.yield(name .. " @ " .. h)
end
end)
end
local hands = { ball("red"), ball("green"), ball("blue") }
for tick = 1, 6 do
local b = hands[(tick - 1) % #hands + 1] -- round-robin
print("tick " .. tick .. ": " .. b())
end
That local h survives across every yield, the way a ball’s spin and arc persist while you’re busy with another one.
The catch is the word cooperative. If one task refuses to yield — an infinite loop, a ball you won’t let go of — everything else freezes. Classic Mac OS and Windows 3.x ran this way, and a single wedged program could hang the whole desktop. Preemption, where a timer forcibly takes the core back, is the modern fix. A juggler doesn’t get preemption. You have to learn to release on schedule, which, three balls in, is precisely the part I can’t do yet.