This guide outlines several techniques for using in-game instance streaming efficiently and effectively. While there's no "one size fits all" solution for designing a streaming game or converting a non‑streaming game to streaming, following these high‑level steps will get you most of the way there.
Streaming properties
Once StreamingEnabled is toggled on for the Workspace object in Studio, set its related properties to the following recommended values:
| Property | Recommendation |
|---|---|
| EnableSLIMAvatars | Use Enabled to render standard rig avatars as lightweight, animated stand‑ins when appropriate. See SLIM avatars for more info. |
| ModelStreamingBehavior | Use Improved to enable the most efficient streaming for Models with BasePart descendants. |
| StreamingIntegrityMode | Use PauseOutsideLoadedArea to balance gameplay integrity without pausing unnecessarily or too often. |
| StreamingMinRadius | Use the default of 64 to maximize how much the engine can scale the game down for low‑end devices. |
| StreamingTargetRadius | Use the default of 1024 to strike a good balance between visibility for players on high‑end devices and a reasonable memory footprint. |
| StreamOutBehavior | Use Opportunistic to allow the client to aggressively garbage collect content, significantly reducing memory usage and helping prevent out‑of‑memory crashes. |
Model level-of-detail
Model.LevelOfDetail helps fill in non‑streamed Model content with lightweight composite or imposter meshes, making the world look visually complete. SLIM (Scalable Lightweight Interactive Model) composite meshes are particularly effective, as players often cannot distinguish a SLIM mesh from the fully streamed‑in original.
For best results:
- Group parts that are spatially and logically related, for example all the parts of a car.
- Set LevelOfDetail to SLIM on models that contain static meshes and parts. Models that contain skinned meshes, are modified at runtime, or play animations are not supported.
- Keep the spatial extent of each model under ~64 cubic studs to increase the likelihood that the entire actual model streams in together. If a model has very large extents, break it up into smaller modular models and apply an appropriate LevelOfDetail to each one.
Model structure
Beyond setting model level‑of‑detail, the structure and settings of your Models has a significant impact on how well streaming performs. As you build out or convert an existing game:
Use atomic models for logical grouping — When a script needs access to all of the parts within a model, set its ModelStreamingMode to Atomic. This allows client‑side scripts to safely access instances inside the model without excessive use of WaitForChild() (although such scripts must still use WaitForChild() for the overall atomic model).
Minimize persistent models — Persistent models load after join and never stream out, permanently occupying memory. Set a model's ModelStreamingMode to Persistent only if it must remain available and accessible to scripts at all times.
Decompose container models — A common non‑streaming pattern is a single huge Model that contains many NPCs, props, or similar groupings. Under streaming, container models decrease streaming efficiency and they aren't optimal for model level‑of‑detail which works best with closely grouped instances. Decompose container models into smaller models with physically close or logically related parts.
Flatten deeply nested model hierarchies — Nesting a persistent model inside an atomic model effectively forces the atomic model to behave as persistent. Flat hierarchies are easier to reason about under streaming.
SLIM avatars
Platform avatars outside of the currently streamed area are not visible by default, but enabling Workspace.EnableSLIMAvatars renders standard rig avatars as lightweight, animated stand‑ins when appropriate. Effectively, the engine:
- Renders a SLIM version when an actual avatar model streams out.
- Swaps SLIM/actual by available resources inside the streaming radius, as SLIM models can be significantly less costly to render than the full resolution model.
- Throttles SLIM animations by scene importance and bandwidth.
The avatar level-of-detail system has specific optimizations for avatars. The following describes what will or won't be processed by SLIM:
Script patterns
The following script patterns are most commonly affected by streaming. The correct strategy depends on the intent of the code, so each pattern lists multiple options where appropriate.
Direct index to descendants
Indexing into Workspace descendants with the . operator throws an error if any instance in the path isn't currently streamed in. The same applies to FindFirstChild(), FindFirstChildWhichIsA(), and FindFirstChildOfClass() which return nil if the child has not streamed in.
Descendant Lookuplocal house1 = workspace:FindFirstChild("House1") -- nil if "House1" has not streamed inlocal door = workspace.House1.Door -- Broken if "House1" or "Door" has not streamed in
A similar pattern is accessing the Humanoid or other character descendants directly inside a Player.CharacterAdded connection. Under streaming, the character model is parented to Workspace before all of its descendants have replicated, so direct indexing fails.
Character Descendants
local Players = game:GetService("Players")
local player = Players.LocalPlayer
player.CharacterAdded:Connect(function(character)
local humanoid = character.Humanoid
end)
If the script cannot make progress without an instance, wait for it with WaitForChild():
Descendant Lookuplocal house1 = workspace:WaitForChild("House1")local door = house1:WaitForChild("Door")
Instances sent remotely
A RemoteEvent/RemoteFunction signal and the instance it refers to travel independently, so the signal can arrive on the client before the instance is present — or the instance may never be present at all. Two likely causes include:
Under streaming, there may be a slight delay between when a part/model is created on the server and when it gets replicated to clients. Effectively, a part referenced by a RemoteEvent/RemoteFunction may simply not exist yet, even inside a streamed area.
Sending a part/model reference from server to client through a RemoteEvent or RemoteFunction requires that the instance is replicated to the receiving client. Sending an instance path as a string has the same issue, as the path may resolve to a non‑existent location on the client:
Client Scriptlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")remoteEvent.OnClientEvent:Connect(function(data)local checkpoint = data.checkpoint -- Errors if "checkpoint" isn't streamed inlocal level = workspace.Levels[data.levelPath] -- Errors if path isn't streamed inend)
If the receiving client script needs the instance to proceed, include WaitForChild() before using it. Note that this can yield indefinitely if the instance never streams in, so consider adding a timeout as the second parameter of WaitForChild().
Client desynchronization
Client‑side desynchronization should be treated as an exception, not a standard design pattern. Introducing client‑only copies or reparenting instances locally can create serious issues. Audit your code for places which rely on these types of changes persisting on the client.
For example, reparenting an instance locally from ReplicatedStorage to Workspace can make that instance eligible to be streamed out. Similarly, cloning an instance locally (Instance:Clone()) from ReplicatedStorage into Workspace creates a client‑only copy that is no longer part of the server replication pipeline and will not receive property updates from the original server‑owned instance.
The same concept applies when calling Instance:Destroy() on the client for a server‑owned object. This removes the instance locally but the server still has it, so it will stream in again with its original state when eligible.
Proactive streaming
When a player's next destination can be anticipated, make server‑side calls to Player:RequestStreamAroundAsync() to stream in transient areas for temporary loading, or use Player:AddReplicationFocus() on a limited basis for areas that should remain loaded until explicitly released.
For example, when a player character is about to teleport by CFrame change to another player's house in a distant location, you can pre‑fetch the destination area to minimize pop‑in and provide a smoother transition. The following script shows how a client-to-server remote event can be fired to move a player character using a pre-fetching method. If the pre-fetch request is successful when the function returns, the minimum radius around the target location should be present on the client.
Server Script - Teleport Player Character
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")
local function teleportPlayer(player, teleportTarget)
-- Request streaming around target location
player:RequestStreamAroundAsync(teleportTarget)
-- Teleport character
local character = player.Character
if character and character.Parent then
local currentPivot = character:GetPivot()
character:PivotTo(currentPivot * CFrame.new(teleportTarget))
end
end
-- Call teleport function when the client fires the remote event
teleportEvent.OnServerEvent:Connect(teleportPlayer)
Instance property reads
Once an instance streams out, its property updates are no longer replicated to that client. Reading properties such as BasePart.Position continue to succeed but return the last replicated value which can be arbitrarily stale.
local Players = game:GetService("Players")local player = Players.LocalPlayer-- Position may be stale if "target" has streamed outlocal dist = (target.Position - player.Character.HumanoidRootPart.Position).Magnitude
Move the logic to the server, as server‑side scripts see all instances at all times. This is generally the most reliable option for distance checks and other position‑sensitive logic.
Waiting on the critical path
Some non‑streaming games load their map by cloning it from ReplicatedStorage into Workspace, then wait for it on the client before dismissing a loading screen and signaling readiness. Under streaming this hangs indefinitely — the client's character hasn't spawned yet, so there's no replication focus, and the spatial map instance never streams in.
Move the loading screen logic so that it doesn't depend on a specific spatial instance being present, for example by signaling readiness once the character has spawned and the immediate surrounding area is streamed in.
Signal change handling
Signals such as Instance.ChildAdded/Instance.ChildRemoved and CollectionService signals like GetInstanceAddedSignal() or GetInstanceRemovedSignal() also fire on stream in/out, indistinguishable from real spawns/removals. Receiving scripts can't tell the difference from the signal alone, so logic which assumes a signal corresponds to a "real" event needs to be updated.
Audit scripts for any signal listeners that could break or change significantly when triggered by stream in and/or stream out. For example, if you play audio or visual effects when an enemy NPC initially spawns into the world, assign each enemy an attribute such as Spawned on the first spawn, and skip replaying the same audio/effects in future stream‑ins of the enemy.
Attribute Tracking
local CollectionService = game:GetService("CollectionService")
local TAG_NAME = "Enemy"
CollectionService:GetInstanceAddedSignal(TAG_NAME):Connect(function(enemy)
if not enemy:GetAttribute("Spawned") then
-- Set "Spawned" attribute on enemy for initial spawn
enemy:SetAttribute("Spawned", true)
-- Play audio/visual effects for this initial spawn
playSpawnEffects(enemy)
end
end)
Iteration over collections
Client-side collection iterations like Instance:GetChildren() and Instance:GetDescendants() return only the streamed‑in subset of descendants. This applies even when the parent itself is always replicated, such as a Folder directly under Workspace whose spatial descendants stream in and out.
local Players = game:GetService("Players")local player = Players.LocalPlayer-- Folder "Homes" is always replicated but its children stream in and out-- This loop may miss homes that aren't currently streamed infor _, home in workspace.Homes:GetChildren() doif home.Settings.Owner.Value == player.Name thenreturn homeendend
If a complete enumeration is required, perform the scan on the server and pass the result to the player through a RemoteEvent if needed.
Spatial queries
Client‑side spatial queries like WorldRoot:Raycast(), WorldRoot:GetPartBoundsInBox(), and Model:GetBoundingBox() reflect only streamed‑in content. Whether that's a problem depends on what the query is used for.
Use the server for queries whose result must reflect the full world, for example a raycast that checks whether the player has line of sight to a distant target.
Other patterns
The following patterns also may also apply and should be carefully considered:
A Sound or AudioPlayer parented to a 3D object stops when that object streams out. For ambient audio that should persist regardless of streaming, parent the emitter to a persistent model or to a non‑streaming container.
In-game UI objects like BillboardGui or SurfaceGui as well as visual effects like Beams or Highlights whose adornee or attachment streams out simply stop rendering. This may be the intended behavior, but you should verify it.
BasePart.Touched events, ProximityPrompts, DragDetectors, and ClickDetectors do not operate for players whose client doesn't have the associated part/model streamed in. If interaction must be possible from any range, the model needs to be persistent or the interaction needs a different mechanism.
For PathfindingService and client‑side pathfinding, the pathfinder only sees streamed‑in geometry on the client and it may route through obstacles that exist on the server. See here for strategies.
Realistic test conditions
Once scripts are updated, test the game thoroughly. Streaming bugs often only manifest at the edges of the streamed area or during transitions, so only testing near spawn or at target radius isn't sufficient.
Test with Workspace.StreamingTargetRadius set to its minimum value (64). Some streaming bugs only appear when the streamed area is small.
Play through the full traversal patterns of the game, teleport between distant areas, and revisit areas after leaving them. These are the situations that exercise stream in and stream out the most.
Use the streaming debug overlay to monitor the active streaming settings, currently loaded regions, and runtime streaming state.
Watch the Output window and Developer Console for errors, as many of the script patterns produce errors rather than silent misbehavior. Pay particular attention to errors of the form attempt to index nil with ... which often indicate a missing WaitForChild() call.
Equip and activate Tools, fire weapons, and trigger different game interactions.

