0Pricing
Lua Academy · Lesson

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))  -- dead

The "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: normal

Checking 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, ...)
end

Status 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 coroutine

coroutine.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 thread

Monitoring 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
end

Coroutine 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())   -- 100

Coroutine.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 coroutine

isyieldable 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 successfully

Quick 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

  1. Creating Coroutines
  2. resume and yield
  3. Coroutine Status
  4. Producer-Consumer Pattern
← Back to Lua Academy