Coroutine Status
Check coroutine state with coroutine.status: running, suspended, dead.
Coroutine Status is a free Lua Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Four Coroutine States
Coroutines have four possible states: suspended (created or yielded, waiting to run), running (currently executing), normal (paused to let another coroutine run), and dead (finished or errored). Query with coroutine.status(co).
local co = coroutine.create(function()
print(coroutine.status(coroutine.running())) -- running
coroutine.yield()
end)
print(coroutine.status(co)) -- suspended
coroutine.resume(co) -- prints: running
print(coroutine.status(co)) -- suspended
coroutine.resume(co)
print(coroutine.status(co)) -- deadThe "normal" State
A coroutine enters the "normal" state when it resumes another coroutine. It's not running (the other coroutine is), but it's not suspended either — it's waiting for the resumed coroutine to yield. This state is rare but important to understand for complex coroutine graphs.
local inner, outer
inner = coroutine.create(function()
print("outer status:", coroutine.status(outer)) -- normal
coroutine.yield()
end)
outer = coroutine.create(function()
coroutine.resume(inner)
end)
coroutine.resume(outer)
-- prints: outer status: normalChecking Before Resume
Always check a coroutine's status before resuming, especially in loops. Resuming a dead coroutine is an error. Resuming a running coroutine is also an error (would cause infinite recursion).
local function safeResume(co, ...)
local status = coroutine.status(co)
if status == "dead" then
return false, "cannot resume dead coroutine"
end
if status == "running" then
return false, "cannot resume running coroutine"
end
return coroutine.resume(co, ...)
endStatus After Error
When a coroutine body raises an uncaught error, coroutine.resume returns false and the error message. The coroutine moves to the "dead" state. It cannot be restarted — you must create a new coroutine if you want to retry.
local co = coroutine.create(function()
error("something went wrong")
end)
local ok, err = coroutine.resume(co)
print(ok, err) -- false ...: something went wrong
print(coroutine.status(co)) -- dead
-- Cannot resume dead coroutine:
local ok2, err2 = coroutine.resume(co)
print(ok2, err2) -- false cannot resume dead coroutinecoroutine.isyieldable
coroutine.isyieldable() returns true if the running coroutine can yield. It returns false in the main thread and in C functions that don't support yielding. Always check before yielding if you're unsure of the context.
local co = coroutine.create(function()
print("isyieldable:", coroutine.isyieldable()) -- true
coroutine.yield()
end)
coroutine.resume(co)
-- In main thread:
print("isyieldable:", coroutine.isyieldable()) -- false
-- coroutine.yield() -- ERROR in main threadMonitoring a Set of Coroutines
Track a pool of coroutines, removing dead ones after each round of resumes. This is the basis of a task scheduler.
local function makeWorker(n, name)
return coroutine.create(function()
for i = 1, n do
print(name, "step", i)
coroutine.yield()
end
end)
end
local workers = {
makeWorker(2, "fast"),
makeWorker(4, "slow"),
}
while #workers > 0 do
local alive = {}
for _, co in ipairs(workers) do
coroutine.resume(co)
if coroutine.status(co) ~= "dead" then
alive[#alive+1] = co
end
end
workers = alive
endCoroutine Debugging
Use status checks to instrument and debug coroutine-based code. Print status transitions to understand the flow. In complex systems, assign names to coroutines via a wrapper table for better debugging output.
local function namedCo(name, fn)
local co = coroutine.create(fn)
return {
co = co,
name = name,
resume = function(self, ...)
print("[" .. self.name .. "] resume")
local results = table.pack(coroutine.resume(self.co, ...))
print("[" .. self.name .. "] status: " .. coroutine.status(self.co))
return table.unpack(results, 1, results.n)
end
}
end
local worker = namedCo("worker1", function()
coroutine.yield()
end)
worker:resume()
worker:resume()Restarting Dead Coroutines
Once a coroutine is dead, it cannot be resumed. To "restart" it, you must create a new coroutine from the same function. A common pattern is a factory that creates a fresh coroutine on demand.
local function makeCounter(start, step)
return coroutine.wrap(function()
local n = start or 0
while true do
coroutine.yield(n)
n = n + (step or 1)
end
end)
end
local count = makeCounter(0, 2)
for i = 1, 5 do io.write(count() .. " ") end
print() -- 0 2 4 6 8
-- "Restart" by creating a new one
count = makeCounter(100)
print(count()) -- 100Coroutine.wrap Error Behavior
When a wrapped coroutine errors, the error propagates out of the wrapper function call. Unlike coroutine.resume which returns false+error, wrap-returned functions raise the error directly. Use pcall around wrap calls if you need to catch errors.
local gen = coroutine.wrap(function()
coroutine.yield(1)
error("oops")
coroutine.yield(2) -- never reached
end)
print(gen()) -- 1 (ok)
local ok, err = pcall(gen)
print(ok, err) -- false ...: oops
-- Subsequent call: dead coroutine
ok, err = pcall(gen)
print(ok, err) -- false cannot resume dead coroutineisyieldable vs status
Two ways to introspect coroutine context: coroutine.status(co) gives the state of a specific coroutine; coroutine.isyieldable() tells if the current execution context can yield. Use status to check others; use isyieldable to check if you can safely yield.
local function maybeYield()
if coroutine.isyieldable() then
coroutine.yield()
else
-- in main thread or non-yieldable context
print("cannot yield here")
end
end
maybeYield() -- prints "cannot yield here" (main thread)
local co = coroutine.create(maybeYield)
coroutine.resume(co) -- yields successfullyQuick Check
What state does a coroutine enter when it finishes executing its body function?
Recap: Coroutine Status
Summary:
- States: suspended → running ↔ normal → dead
coroutine.status(co)returns the state string- Dead = finished or errored; cannot be resumed
coroutine.isyieldable()checks if current context can yield- Always check status before resuming in loops
- wrap errors propagate directly; resume returns false+err
Frequently asked questions
Is the “Coroutine Status” lesson free?
Yes — the full text of “Coroutine Status” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.
What will I learn in “Coroutine Status”?
Check coroutine state with coroutine.status: running, suspended, dead. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Lua Academy?
No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Coroutine Status” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Lua Academy lesson?
Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Creating Coroutines
- resume and yield
- Coroutine Status
- Producer-Consumer Pattern