Modules and require
By the end of this lesson you'll split a program across files: write a module that returns a table, load it with require (which runs once and caches), understand package.path and dotted module names, prefer local over globals to keep the namespace clean, and know why the old module() function is gone.
Learn Modules and require in our free Lua course — an interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Lua course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
A module is a toolbox you keep in one drawer. Inside, the table it return s is the tray of tools you actually hand out; the local helpers are the messy offcuts you keep in the back, out of sight. require is the act of fetching that drawer — and because the workshop is tidy, the first person to open it sets it on the bench (runs it once) and everyone after just borrows the same opened toolbox (the cache). Organise your tools into labelled drawers (folders and dotted names) and the whole workshop stays findable.
1. A Module Returns a Table
In modern Lua a module is simply a file that builds a table of everything it wants to share and return s it on the last line. Public functions and constants go on that table; private helpers stay as local functions so they never leak out. There is no special module keyword — the file is ordinary Lua, and the returned table is its public interface. Read this mymath.lua example and notice how the private clamp helper stays hidden.
2. Loading with require (and Caching)
require("name") finds the module's file, runs it once , and returns whatever it returned — no .lua extension in the name. The crucial detail is caching : require stores the result in package.loaded , so a second require of the same name returns the identical table without re-running the file. That is why a shared config module behaves as one object across your whole program: change it in one place and everyone who required it sees the change.
Your turn. Finish the greet module: uppercase a greeting and, crucially, return the table. Fill in the blanks marked ___ .
3. package.path, Dotted Names & Avoiding Globals
require locates files through package.path , a list of templates where ? is replaced by the module name. Dotted names map dots to folders, so require("shapes.circle") looks for shapes/circle.lua — perfect for organising larger projects. Always assign a required module to a local variable: it keeps dependencies scoped and avoids polluting _G , the global table. The same discipline applies inside modules — keep helpers local so they never collide with names elsewhere.
Now you try loading. Require the greet module into a local and prove that a second require returns the same cached table. Fill in the blanks:
No blanks this time — just a brief and an outline. You'll package a small class into its own file and consume it from another via require , keeping internals local and returning only the table. Build both files and run main.lua .
Practice quiz
What is the standard way for a Lua module to expose its contents?
- By returning a table at the end of the file
- By setting many globals
- By calling export()
- By printing them
Answer: By returning a table at the end of the file. A module file builds a table of its public functions and returns it as the last statement.
Which function loads a module by name?
- import
- include
- require
- load
Answer: require. require('name') finds, runs, and returns the module, caching the result.
How many times does require run a given module's code, no matter how often you require it?
- Never, it only links
- Once, then it is cached
- Every time you call require
- Twice for safety
Answer: Once, then it is cached. require runs the module body once and caches the return value in package.loaded for reuse.
Where does require remember already-loaded modules?
- _G
- package.path
- package.cpath
- package.loaded
Answer: package.loaded. package.loaded is the cache table keyed by module name; a present entry skips re-running.
What does package.path control?
- The search patterns for finding Lua module files
- The OS PATH variable
- The location of the lua binary
- Garbage collection timing
Answer: The search patterns for finding Lua module files. package.path is a list of ? templates that require expands to locate a module's source file.
How is the dotted name require('a.b.c') usually mapped to a file?
- It is ignored
- Dots become directory separators: a/b/c.lua
- It loads a.lua only
- It looks for a-b-c.lua
Answer: Dots become directory separators: a/b/c.lua. By default each dot maps to a path separator, so a.b.c is sought as a/b/c.lua via package.path.
Why should module code declare its internals as local?
- require requires it
- locals run slower but are clearer
- Because globals are illegal in modules
- To avoid leaking globals that could clash with other code
Answer: To avoid leaking globals that could clash with other code. Keeping helpers local prevents polluting the global namespace and accidental name collisions.
What does require return to the caller?
- The module's file path
- Nothing
- The value the module returned (usually a table)
- Always true
Answer: The value the module returned (usually a table). require yields whatever the module returned, which is conventionally a table of functions.
What is the status of the old module() function in modern Lua?
- Required in every module
- Deprecated; return a table instead
- Only way to make modules
- Renamed to require
Answer: Deprecated; return a table instead. The module() function was deprecated after Lua 5.1; current style simply returns a table.
If two files both require('config'), how many config tables exist?
- One shared table from the cache
- Two separate copies
- Zero until both finish
- It depends on package.cpath
Answer: One shared table from the cache. Because require caches, both files receive the very same single table instance.