Skip to content

Scheduling: every, after, and timeout

Zolo has three scheduling constructs built into the language. All are reserved keywords — do not use every, after, or timeout as variable names; prefer interval, scheduled, deadline, or limit instead.

every — periodic loop

every <dur> { ... } repeats the body at the given interval. It is an infinite loop by design: terminate it with break when the exit condition is reached.

A counter with break after 3 iterations; a running sum that stops once the desired total is reached.

05-every.zolo
Playground
// Feature: periodic loop with `every <dur> { ... }`

// Syntax: `every <duration> { body }` — repeats the body at the given

// interval. It is a keyword (not a variable name).

// When to use: health-checks, polling, animation, metrics, any

// task that needs to run repeatedly on a fixed interval.


var tick = 0

// Runs 3 times (short interval for the demo) and exits with `break`.

every 50ms {
  tick = tick + 1
  print("tick #{tick}")
  if tick >= 3 {
    break
  }
}

print("done")

// expected:

// tick #1

// tick #2

// tick #3

// done


// Another example: summing until a total is reached.

var total = 0
every 25ms {
  total = total + 10
  if total >= 30 {
    break
  }
}
print("total = {total}")
// expected: total = 30

after — one-shot firing

after <dur> { ... } schedules the block to run exactly once, after the delay. It is fire-and-forget: it does not block the main flow. Use sleep afterwards to keep the program alive until the callback fires.

Three after calls coexisting; firing order determined by the scheduler.

06-after.zolo
Playground
// Feature: schedule one execution with `after <dur> { ... }`
// Syntax: `after <duration> { body }` — fire-and-forget; the block
// runs **once** after the delay. It is a keyword.
// When to use: one-shot callbacks, timers, expirations, single
// reminders. Unlike `every`, it does not repeat.

print("scheduling...")

// Block runs 50ms later — does not block the main flow here.
after 50ms {
  print("fired after 50ms!")
}

// Multiple `after` may coexist.
after 100ms {
  print("fired after 100ms!")
}

after 25ms {
  print("this one was first")
}

print("scheduled, waiting...")

// Keep the program alive long enough to see the firings.
sleep 200ms
print("end")
// expected (approximate order — depends on the scheduler):
// scheduling...
// scheduled, waiting...
// this one was first
// fired after 50ms!
// fired after 100ms!
// end

timeout — deadline cancellation

timeout <dur> { ... } returns an object { ok, value, error }. If the block completes within the deadline, ok is true and value holds the return. If it expires, ok is false and error is "timeout". Because the scheduler is cooperative, cancellation only occurs at yield points (sleep, IO, etc.).

Happy path with a return within the deadline; note on cooperative behaviour and an alternative name for the deadline variable.

07-timeout.zolo
Playground
// Feature: cancel work that takes too long with `timeout`
// Syntax: `timeout <dur> { body }` — returns a `Result`-like
// (`{ ok, value, error }`) with `ok = true` if it completed, or
// `ok = false, error = "timeout"` if it expired.
// When to use: network calls, uncertain IO, tasks that need a
// fail-safe. Cooperative: only fires at a yield point.

// Happy path: body finishes within the deadline.
let result_ok = timeout 100ms {
  return 42
}
if result_ok.ok {
  print("ok: {result_ok.value}")
} else {
  print("err: {result_ok.error}")
}

// expected: ok: 42

// Timeout case: body takes longer than the limit and is interrupted.
// Note: cooperative — the body must yield (e.g. via `sleep`) for
// the deadline to fire. If it never yields, the body runs to
// completion and `ok = true`.
// SKIP: sleep inside `timeout { ... }` raises a YieldError in the
// current runtime; the failure case is therefore commented out.
//
// let result_slow = timeout 30ms {
//   sleep 200ms
//   return "too late"
// }
// if result_slow.ok {
//   print("ok: {result_slow.value}")
// } else {
//   print("err: {result_slow.error}")
// }
//
// expected: err: timeout

// IMPORTANT: `timeout` is a reserved keyword — you cannot use it
// as a variable name. Use `deadline`, `limit`, `dur`, etc. instead.
let limit = 5000
print("using '{limit}' instead of 'timeout' as the name")

Challenge

Combine every 100ms with an after 500ms that sets a stop flag. The every should check the flag and call break when it is true.

enespt-br