0Pricing
Lua Academy · Lesson

Creating Coroutines

Create coroutines with coroutine.create and coroutine.wrap.

Creating Coroutines is a free Lua Academy lesson on CoddyKit — lesson 1 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.

What is a Coroutine?

A coroutine is a function that can pause its execution (yield) and later resume from where it left off. Unlike threads, coroutines are cooperative — only one runs at a time, and they explicitly yield control. Lua coroutines have their own stack and local variables.

local co = coroutine.create(function()
  print("start")
  coroutine.yield()
  print("resumed")
  coroutine.yield()
  print("done")
end)

print(coroutine.status(co))   -- suspended
coroutine.resume(co)          -- start
print(coroutine.status(co))   -- suspended
coroutine.resume(co)          -- resumed

coroutine.create vs coroutine.wrap

coroutine.create(fn) creates a coroutine and returns a coroutine object (type thread). coroutine.wrap(fn) creates a coroutine and returns a function that, when called, resumes it. wrap is simpler but hides the coroutine object.

-- create: returns coroutine object
local co = coroutine.create(function(x)
  return x * 2
end)
local ok, val = coroutine.resume(co, 5)
print(ok, val)   -- true  10

-- wrap: returns a resumable function
local gen = coroutine.wrap(function(x)
  return x * 3
end)
print(gen(5))    -- 15

Coroutine Lifecycle

A coroutine has four states: suspended (not running), running (currently executing), dead (returned or errored), and normal (paused because it resumed another coroutine). Check state with coroutine.status(co).

local co = coroutine.create(function()
  coroutine.yield()
end)

print(coroutine.status(co))  -- suspended
coroutine.resume(co)          -- runs until yield
print(coroutine.status(co))  -- suspended again
coroutine.resume(co)          -- runs until end
print(coroutine.status(co))  -- dead

Running Multiple Coroutines

Create multiple coroutines and interleave them by resuming each in turn. This simulates concurrent execution (cooperative multitasking). Each coroutine keeps its own state between yields.

local function worker(name, steps)
  for i = 1, steps do
    print(name .. ": step " .. i)
    coroutine.yield()
  end
end

local co1 = coroutine.create(function() worker("A", 3) end)
local co2 = coroutine.create(function() worker("B", 3) end)

for _ = 1, 3 do
  coroutine.resume(co1)
  coroutine.resume(co2)
end
-- A:1, B:1, A:2, B:2, A:3, B:3

coroutine.wrap Iteration

coroutine.wrap is commonly used to create iterators. The returned function is called each time the iterator advances. When the coroutine body ends, the function raises an error — use pcall or check status if needed.

local function range(n)
  return coroutine.wrap(function()
    for i = 1, n do
      coroutine.yield(i)
    end
  end)
end

for v in range(5) do
  io.write(v .. " ")
end
print()  -- 1 2 3 4 5

Error in Coroutines

If a coroutine raises an error, coroutine.resume returns false followed by the error message. The coroutine transitions to the dead state. A dead coroutine cannot be resumed again.

local co = coroutine.create(function(x)
  if x < 0 then error("negative input: " .. x) end
  return math.sqrt(x)
end)

local ok, val = coroutine.resume(co, -1)
print(ok, val)    -- false  input:2: negative input: -1
print(coroutine.status(co))  -- dead

-- Can't resume a dead coroutine:
ok, val = coroutine.resume(co)
print(ok, val)    -- false  cannot resume dead coroutine

Coroutine Identity

coroutine.running() returns the currently running coroutine and a boolean indicating if it's the main thread. Use this inside a coroutine to get a reference to itself.

local co = coroutine.create(function()
  local self, isMain = coroutine.running()
  print("type:", type(self))      -- thread
  print("isMain:", isMain)        -- false
  print("status:", coroutine.status(self)) -- running
end)

coroutine.resume(co)

-- In main thread:
local main, isMain = coroutine.running()
print("main isMain:", isMain)    -- true

Infinite Generator

A coroutine body can contain an infinite loop with yields inside — the generator produces values on demand without terminating. This is a key use case: lazy, infinite sequences.

local function naturals(start)
  return coroutine.wrap(function()
    local n = start or 1
    while true do
      coroutine.yield(n)
      n = n + 1
    end
  end)
end

local gen = naturals(1)
for i = 1, 8 do
  io.write(gen() .. " ")
end
print()  -- 1 2 3 4 5 6 7 8

Coroutines as Tasks

A basic task scheduler: store coroutines in a queue and resume each in turn. When a coroutine yields, it goes back in the queue. When it dies, it's removed. This is a minimal cooperative scheduler.

local tasks = {}

local function spawn(fn)
  tasks[#tasks+1] = coroutine.create(fn)
end

local function run()
  while #tasks > 0 do
    local alive = {}
    for _, co in ipairs(tasks) do
      coroutine.resume(co)
      if coroutine.status(co) ~= "dead" then
        alive[#alive+1] = co
      end
    end
    tasks = alive
  end
end

spawn(function() for i=1,3 do print("T1:"..i); coroutine.yield() end end)
spawn(function() for i=1,3 do print("T2:"..i); coroutine.yield() end end)
run()

Coroutine Memory

Each coroutine has its own stack. Stacks can grow large for deep call chains. In high-performance scenarios, avoid creating thousands of simultaneously live coroutines. Reuse coroutines when possible, or use pools.

-- Coroutine pool concept
local pool = {}

local function getCoroutine(fn)
  -- Reuse dead coroutine slots
  for i, co in ipairs(pool) do
    if coroutine.status(co) == "dead" then
      pool[i] = coroutine.create(fn)
      return pool[i]
    end
  end
  local co = coroutine.create(fn)
  pool[#pool+1] = co
  return co
end

print("Pool pattern for coroutine reuse")

Quick Check

What state is a coroutine in immediately after coroutine.create(fn)?

Recap: Creating Coroutines

Summary:

  • coroutine.create(fn) → coroutine object; wrap(fn) → resumable function
  • States: suspended → running → suspended/dead
  • coroutine.resume(co, ...) starts/resumes
  • Errors during resume: false + error message; coroutine → dead
  • Infinite generators via while true do yield() end
  • Use for cooperative multitasking and lazy sequences

Frequently asked questions

Is the “Creating Coroutines” lesson free?

Yes — the full text of “Creating Coroutines” 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 “Creating Coroutines”?

Create coroutines with coroutine.create and coroutine.wrap. 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 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Creating Coroutines” 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