Tutorials•November 8, 2026•8 min read

Roblox ModuleScript: require, Return Values, and Separate State

Learn Roblox ModuleScripts with a complete two-file counter exercise. Use require, expose functions, understand cached references, and create separate instances.

A Roblox ModuleScript packages reusable code behind a returned value. Another script loads it with require, then calls the functions or reads the data it exposes. Start with one small module and one caller so you can see the dependency clearly before splitting a whole game into files.

Two require calls reference the same module API while two constructor calls create independent counter state
Original diagram of the two-file exercise. Shared module access and independent counter instances are separate ideas.

What a ModuleScript returns

The ModuleScript reference shows a module returning one value and a caller retrieving it with require. A table is useful when you want a named collection of functions. A function defined only as a local inside the module is not automatically part of that returned API.

Think about the interface before moving code. A counter needs operations such as “read” and “increment”; the caller does not need to know how the count is stored. Keeping that boundary small makes it easier to change implementation without changing every consumer.

Create these two objects in Studio

Exact setup for this server-only practice exercise
ParentObject typeExact nameRole
ServerScriptServiceModuleScriptPracticeCounterReturns the counter constructor
ServerScriptServiceScriptCounterPracticeRequires the module and prints results

Use a separate practice place and keep the two objects as direct siblings. Put the module code below into PracticeCounter and the caller code into CounterPractice. Open Output and start a play test. No pre-existing Parts, player data, or external module assets are required.

ModuleScript: return a small constructor API

local PracticeCounter = {}

function PracticeCounter.new()
    local value = 0
    local counter = {}

    function counter.read()
        return value
    end

    function counter.increment()
        value += 1
        return value
    end

    return counter
end

return PracticeCounter

PracticeCounter.new is the exposed constructor. Each call creates a new local value and a new counter table whose functions use that value. The module returns PracticeCounter, while new returns one counter. Those are two different return points with different responsibilities.

The example uses dot calls consistently. Its functions capture the local value and do not use a self parameter. Follow the API’s calling convention when adapting other code; do not switch between dot and colon notation without understanding the parameters expected by that API.

Script: require the module and create two counters

local moduleObject = script.Parent:WaitForChild("PracticeCounter")
local PracticeCounter = require(moduleObject)
local SameModule = require(moduleObject)

print("Same module reference:", PracticeCounter == SameModule)

local first = PracticeCounter.new()
local second = SameModule.new()

print("Initially:", first.read(), second.read())
first.increment()
first.increment()
print("After first changes:", first.read(), second.read())
second.increment()
print("After second changes:", first.read(), second.read())

The intended output values are:

Same module reference: true
Initially: 0 0
After first changes: 2 0
After second changes: 2 1

These are predicted values from the code, not a captured Studio run. The example has been documentation-reviewed but has not been executed here in Roblox Studio. Run it in your own place and inspect Output. Stop and restart the test between changes.

Why requiring twice does not create two module APIs

Roblox’s Reuse code guide explains that repeated require calls return the same reference within the same environment. Requiring from the server and a client produces separate references on each side. A shared code location does not make one shared server/client memory table.

In this exercise, both require calls happen in one server Script. The equality check therefore examines that one environment. The calls to new are what create separate counters. Do not use this exercise to infer cache behavior across Actors or other execution environments it does not test.

If you instead put one mutable count directly on the returned module table, callers sharing that table would be reading and writing the same entry. That may be intentional for a single controller, but it is a poor surprise when you expected one independent count per object.

Choose storage based on who needs the code

This exercise uses ServerScriptService, which the reference marks as not replicated. Roblox’s reuse guide describes ReplicatedStorage as a common location when server and client scripts both need a module’s code.

Keep server-owned decisions in the server path. Moving reusable configuration into a shared location does not authorize a client to award progress or change authoritative state. Define which side owns each decision before reusing the same helper from both sides.

Do not move these two files into different containers without also updating the caller’s reference. script.Parent in CounterPractice currently means ServerScriptService. The named sibling lookup is a deliberate part of this exercise, not a universal path for every module.

Debug the path, return value, and function name

  • Nothing prints: verify that CounterPractice is a running regular Script in the stated server location.
  • The lookup waits: check the exact sibling name PracticeCounter, including capitalization.
  • A function is missing: inspect what the module actually returns and whether that table contains the function.
  • The module errors: inspect its first error in Output before debugging the caller’s later symptoms.
  • Two objects share state: check whether you called new twice or reused one returned instance.
  • Old results remain confusing: restart the test and remove duplicate practice callers.

Waiting for a correctly named child cannot repair an incorrect hierarchy. Verify the object in Explorer first. Use the script search guide to find other consumers or duplicate writes when a module is used in more than one place.

Avoid circular dependencies and hidden startup work

The reuse guide warns about modules requiring each other recursively. Keep a simple direction: the caller depends on the helper; the helper does not require its own caller. When two helpers need the same plain configuration, a smaller shared dependency can be easier to understand than mutual imports.

Also decide what happens when a module is loaded. This PracticeCounter module defines functions and returns its API; it does not start a background loop or connect game events at load time. If a larger controller needs startup and shutdown behavior, expose and document those operations so consumers know who owns the lifetime.

Reuse one responsibility at a time

A good next module is something you can describe in one sentence: format a label, calculate a value, or create a small independent state object. Separate reusable logic from the place-specific wiring that finds objects and connects events.

For observable object values, continue with Roblox attributes. For the difference between starting work and pausing a caller, use the task-library comparison. For one controller managing tagged objects, see CollectionService. For choosing and handling the lookup, see the WaitForChild and timeout guide. For the returned table’s structure and reference behavior, read the Luau tables guide. Documentation checked November 8, 2026.

Frequently Asked Questions

Does a ModuleScript run automatically like a normal Script?▾

The module is loaded through require. The practice caller retrieves its returned API and explicitly calls the constructor and counter functions.

Does requiring the same module twice create independent state?▾

Repeated require calls in the same environment return the same reference. In this exercise, separate calls to the new constructor create the independent counter state.

Can a module table synchronize the server and every client?▾

No. The reuse guide explains that requiring a module on the server and client produces separate references. Shared code does not mean shared memory across that boundary.

Where do the two practice files go?▾

Create a ModuleScript named PracticeCounter and a regular Script named CounterPractice as direct children of ServerScriptService. The caller uses that sibling relationship.

Ready to build your Roblox game?

Create, edit, preview, and export your game for free. No credit card required.

Try Obby — 100% Free →