With the Parallel Luau programming model, you can run code on multiple threads simultaneously, which can improve the performance of your experience. As you expand your experience with more content, you can adopt this model to help maintain the performance and safety of your Luau scripts.
Parallel Programming Model
By default, scripts execute sequentially. If your experience has complex logic or content, such as non-player characters (NPCs), raycasting validation, and procedural generation, then sequential execution might cause lag for your users. With the parallel programming model, you can split tasks into multiple scripts and run them in parallel. This makes your experience code run faster, which improves the user experience.
The parallel programming model also adds safety benefits to your code. By splitting code into multiple threads, when you edit code in one thread, it doesn't affect other code running in parallel. This reduces the risk of having one bug in your code corrupting the entire experience, and minimizes the delay for users in live servers when you push an update.
Adopting the parallel programming model doesn't mean to put everything in multiple threads. For example, the Server-side Raycasting Validation sets each individual user a remote event in parallel but still requires the initial code to run serially to change global properties, which is a common pattern for parallel execution.
Most times you need to combine serial and parallel phases to achieve your desired output, since currently there are some operations not supported in parallel that can prevent scripts from running, such as modifying instances in parallel phases. For more information on the level of usage of APIs in parallel, see Thread Safety.
Splitting Code into Multiple Threads
To run your experience's scripts in multiple threads concurrently, you need to split them into logical chunks under different Actor instances in the Explorer. Actors inherit from DataModel and work as units of execution isolation that distribute the load across multiple cores running simultaneously.
Placing Actor Instances
You can put Actors in proper folders or use them to replace the top-level instance types of your 3D entities such as NPCs and raycasters, then add corresponding Scripts, LocalScripts, and ModuleScripts.

For most situations, you shouldn't put an Actor instance underneath another Actor in the DataModel. However, if you decide to place a script nesting within multiple Actors for your specific use case, the script is owned by its closest ancestor Actor.

Desynchronizing Threads
Though putting scripts under Actor instances grants them the capability for parallel execution, by default the code still runs on the single thread serially, which doesn't improve the runtime performance. You need to call the task.desynchronize(), a yieldable function that suspends the execution of the current coroutine for running code in parallel and resumes it at the next parallel execution opportunity. To switch a script back to serial execution, call task.synchronize().
Alternatively, you can use RBXScriptSignal:ConnectParallel() method when you want to schedule a signal callback to immediately run your code in parallel upon triggering. You don't need to call task.desynchronize() inside the signal callback.
Desynchronize a Thread Using RBXScriptSignal:ConnectParallel()
RunService.Heartbeat:ConnectParallel(function ()
... -- some parallel code that computes a state update
task.synchronize()
... -- some serial code that changes the state of instances
end)
Scripts that are part of the same Actor always execute sequentially with respect to each other, so you need multiple Actors. For example, if you put all parallel-enabled behavior scripts for your NPC in one Actor, they still run serially on a single thread, but if you have multiple NPC Actors, each of them runs in parallel on its own thread. For more information, see Best Practices.


Thread Safety
During the parallel execution, you can access most instances of the DataModel hierarchy as usual, but some API properties and functions aren’t safe to read or write. If you use them in your parallel code, Roblox engine can automatically detect and prevent these accesses from occurring.
API members have a thread safety level that indicates whether and how you can use them in your parallel code, as the following table shows:
Safety Level | For Properties | For Functions |
---|---|---|
Unsafe | Cannot be read or written. | Cannot be called. |
Read Parallel | Can be read but not written. | N/A |
Local Safe | Can be used within the same Actor; can be read but not written to by other Actors. | Can be called within the same Actor; cannot be called by other Actors. |
Safe | Can be read and written. | Can be called. |
You can find thread safety tags for API members on the API reference. When using them, you should also consider how API calls or property changes might interact between parallel threads. Usually it's safe for multiple Actors to read the same data as other Actors but not modify the state of other Actors.
Example: Server-side Raycasting Validation
For a fighting and battle experience, you need to enable raycasting for your users' weapons. With the client simulating the weapons to achieve good latency, the server has to confirm the hit, which involves doing raycasts and some amount of heuristics that compute expected character velocity, and look at past behavior.
Instead of using a single centralized script that connects to a remote event that clients use to communicate hit information, you can run each hit validation process on the server side in parallel with every user character having a separate remote event.
The server-side script that runs under that character’s Actor connects to this remote event using a parallel connection to run the relevant logic for confirming the hit. If the logic finds a confirmation of a hit, the damage is deducted, which involves changing properties, so it runs serially initially.
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Create new remote event and parent it to the tool
remoteEvent.Parent = tool
remoteEvent.Name = "RemoteMouseEvent" -- Rename it so that the local script can look for it
local remoteEventConnection -- Create a reference for the remote event connection
-- Function which listens for a remote event
local function onRemoteMouseEvent(player : Player, clickLocation:CFrame)
-- SERIAL: execute setup code in serial
local character = player.Character
-- Ignore the user's character while raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {character}
-- PARALLEL: Perform the raycast in parallel
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Used to extend the ray slightly since the click location might be slightly offset from the object
local lookDirection = (1 + epsilon) * (clickLocation.Position - origin)
local raycastResult = workspace:Raycast(origin, lookDirection, params)
if raycastResult then
local hitPart = raycastResult.Instance
if hitPart and hitPart.Name == "block" then
local explosion = Instance.new("Explosion")
-- SERIAL: the code below modifies state outside of the actor
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Make the explosion non-deadly
explosion.Position = clickLocation.Position
-- Multiple Actors could get the same part in a ray cast and decide to destroy it
-- this is actually perfectly safe, but it means that we'll see two explosions at once instead of one
-- so we can double check that we got to this part first here.
if hitPart.Parent then
explosion.Parent = workspace
hitPart:Destroy() -- destroy it
end
end
end
end
-- Connect the signal in serial initially since some setup code is not
-- able to run in parallel.
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
Best Practices
To apply the maximum benefits of parallel programming, refer to the following best practices when adding your Lua code:
Avoiding Long Computations
Even in parallel, long computations can block execution of other scripts and cause lag. Avoid using parallel programming to handle a large volume of long, unyielding calculations.

Using the Right Number of Actors
For the best performance, use more Actors. Even if the device has fewer cores than Actors, the granularity allows for more efficient load balancing between the cores.

This doesn't mean you should use as many Actors as possible. You should still divide code into Actors based on logic units rather than breaking code with connected logic to different Actors.
For example, if you want to enable raycasting validation in parallel, it’s reasonable to use 64 Actors and more instead of just 4, even if you’re targeting 4-core systems. This is valuable for scalability of the system and allows it to distribute the work based on the capability of the underlying hardware. However, you also shouldn't use too many Actors, which are hard to maintain.