Avoiding wait() and why

This is an adaptation of a Twitter thread I made a while ago. This is simply a better formatted and more fleshed out version.


Let’s talk about wait(). Not wait(n) (although if you are using small n values to where it’d be barely distinguishable from wait(), you’re probably going to hit the same issues mentioned in this thread).

We probably learned about wait() at first as the solution to “why are my infinite loops crashing me?”. It’s a super easy solution and it works! This starts a bad habit of just putting wait() to fix your code.

In my opinion, wait() has no place in production code.

Common Usages of wait()

Let’s take this piece of code:

local flag = false

someAsyncThing(function()
    -- bla bla bla
    flag = true -- we're done!
end)

repeat wait() until flag
-- flag is ready!

The repeat wait() until is being used to stop code execution until flag is true. This is known as polling (colloquially, you’ll also hear it called busy waiting in circles for higher level software, although they’re not technically the same). Polling in our case is bad because we’re unnecessarily waiting for something to happen instead of just doing code once it happens.

Consider this scenario:

  • flag is set to false.
  • someAsyncThing calls the callback immediately with no yielding. flag is now true.
  • repeat wait() until flag is ran. Despite flag being true, we’re still waiting for no reason.
  • Code continues.

“Ah, but wait!”, you cry, “I can do this!”

local flag = false

someAsyncThing(function()
    -- bla bla bla
    flag = true -- we're done!
end)

while not flag do
    wait()
end
-- flag is ready!

Yes, you can. However, you’re still unnecessarily waiting even after flag is set to true.

Consider this scenario:

  • flag is false.
  • The callback to someAsyncThing isn’t ran right away.
  • “Is flag true? No? Alright, give me a bit.”
  • flag is set to true! Can our code run yet?
  • “No, sorry, still waiting.”
  • “Is flag true? Yes, it is. Continuing code.”

Our code unnecessarily waited despite it being completely ready to run! The alternative is to simply hook your code up to an event.

If we assume someAsyncThing will never call before the last line (Roblox Lua is single threaded so unless that’s a defined behavior, it won’t)…

local event = Instance.new("BindableEvent") -- You could use coroutines, but they're a bit finnicky

someAsyncThing(function()
    -- bla bla bla
    event:Fire()
end)

event.Event:Wait()

This is the exact same number of lines of code as the repeat wait() until block. Let’s analyze this code again with the last scenario.

  • The callback to someAsyncThing isn’t ran away, and it’s not documented that it can.
  • “Tell me when you’re ready for me to run”
  • someAsyncThing’s callback is called. “Hey, I’m ready for you to run now”
  • We instantly get the message, and continue our code.

“But someAsyncThing can be called immediately in my case!”

In that case, you can do its admittedly uglier cousin:

local event, called = Instance.new("BindableEvent"), false

someAsyncThing(function()
    -- bla bla bla
    called = true
    event:Fire()
end)

if not called then
    event.Event:Wait()
end

Again, this will be ran instantly. No unnecessary polling. Plus, this is milliseconds/seconds saved. Not measly microseconds.

“That’s for my own code, but what if I want to wait for some Roblox code to run.”

You mean like this?

repeat wait() until LocalPlayer.Character

Still no. Roblox has events for everything you could possibly be waiting for (and if it doesn’t make a feature request!). You can yield on an event by calling :Wait() (no relation).

LocalPlayer.CharacterAdded:Wait()

This will yield the thread until the player’s character is added. And I know, I know, “what if the character already exists”? Feast your eyes on this beauty:

local character = player.Character or player.CharacterAdded:Wait()

Witnessing this level of elegance is dangerous to our feeble human brains. It gets the character and if it doesn’t exist, will wait until it’s added. It will be ran the instant the character is added.

Useful events to yield on in no particular order:

  • AncestryChanged
  • ChildAdded
  • ChildRemoved
  • CharacterAdded
  • RunService.Heartbeat

“I use wait() because of a Roblox bug”

We’ve all been there. Sometimes there’s just some weird bug that wait() seems to fix. Once you’re certain it’s a Roblox bug and there’s no event you can use, you still shouldn’t use wait(). Have you considered:

RunService.Heartbeat:Wait() or RunService.Stepped:Wait()?

These are at least 2x faster than wait() and I’ve never had an issue where they didn’t work but wait() did.

“Okay, but why should I care?”

wait() is code smell, meaning using it is the sign of a design problem. Polling is bad when event based programming is staring right at you. Not only is it a design problem, but it has its own issues. A major issue is that wait() (and also wait(n)) can wait for MUCH longer than you want it to. The more you call wait(), the longer it will take. wait() can easily last seconds, leading to a very laggy game experience. Take your pick on which of those is more important to you.

In summary: you don’t need wait(), ever. :Wait() works basically all of the time, and Heartbeat:Wait()/Stepped:Wait() fill in the gaps that aren’t filled.

838 Likes
Is there a way to write whole game codebases without the use of arbitrary waits?
(problem) wait() not being 100% accurate
Wait() taking absurd amounts of time
2D Player Movement
Golang's defer or similar function
Are loops really that scary?
Best Way to Wait for Condition or Time
One time Key Down
Is this method of yielding any better than wait()?
Is repeat wait() bad?
Wait() Based on Framerate
Polygon Triangulation
How intensive is tick()?
Should I ever use wait() function?
Any Tips to Improve my PlayerClass?
What is the most efficient way to check if (condition) has happened?
Making a round timer without using wait?
Wait function alternative?
Wait() taking too much time
Is it good to use repeat wait() loops for this? Is there a more efficient method?
Tick() or wait() ? Wait for minutes?
Game script timeout
Loading intro not working?
How do I make this Part's CFrame Always be equal to the Players CFrame?
Roblox game crashes for mobile users?
Will this cause lag?
Unable To Get Song Name
How do I make this car spawner script have a cooldown?
In-Game System Messages
Loops executing function incorrect amount of times
Best Scripting Practices?
How can I make my CFrame movement code non-hardcoded?
Loading Screen won't show up
PlayerAdded is not creating my instance.new
Why is it that when the Player's Character touches the ground, it gives delayed signals to clients? Please help
Developing an Anti Exploit script. Would love corrections
Bug in my script for showing a view from a camera until the play button is pressed
How to make a Motor turn smoothly
One second delay after timer countdown ends
Script doesn't work
How to make my compass show direction to the part?
Setting part CFrame rotation lagging
How do you make a part vibrate?
'Transferring' text from a TextBox to a TextLabel
Ways for multiple player location checks without polling
Teleporting part to Local Player not working?
Attempt to compare number and string
Weapon server lag
Could I have some examples of BindableEvents and BindableFunctions
How much can roblox as an engine handle?
How to Squash "ghost" Bugs
Instant Regeneration Problem?
Whille wait() do loops causing lag
Third Person Camera Script
Effective Server-sided cooldown
_ is not a valid member of model when it clearly is
Problems with shift to sprint script
Script Help Please
Tracking time in Leaderstats
Combat not working after death
Pathfinding bot freezes when it approaches its target
How can I make it so the player can't move the camera angle?
While true do loop speeds up
Anti-Exploit Not Working
Change the speed of all players
Waits in coroutines are stopping the whole script
Lookvector change
Enhancing Projectile?
Using while loops as modified waits?
Is there a more efficient way of writing this?
How to make an ImageLabel keep rotating around?
How would I make it so that anyone that survives the round gets a win, not just the last person alive?
MoveTo In repeat loops, inefficient?
Need Help understanding RunService
3 scripting questions that I've got on my mind for a long time
3 scripting questions that I've got on my mind for a long time
Issue with Camera rotating around a focus point
How to don't wait to function to end?
Script Keeps Restarting when a Click Event Occurs
Script Keeps Restarting when a Click Event Occurs
Help optimizing
Why is my loop not working correctly?
Add All Values not working
Best method to keep track of time for in-game timers?
How can I improve my script's efficiency? sometimes it has damage delays
Obby Movement Depending On Client Internet Connection
Elegant way to set match length to 180 seconds
How to wait for a function return?
While true do loops
Game's performance decreases overtime
How do you make a projectile go into the direction an object is facing?
Loading Scripts and Remote Event Timing Question
How to wait till a bollean value is true?
Which loop method is less memory intense?
Which method is the best performance-wise?
For i,v loop not firing properly
Custom Face Help
Rainbow Items Script From client
Rainbow Items Script From client
Can someone explain all the events of run service in detail?
Scriptable Camera Issue
Rate my DOF code
Rainbow Items Script From client
How do you cancel a for loop immediately?
How do you cancel a for loop immediately?
UserHasBadgeAsync Working But Occuring An Error
Tool assigning script works in studio but not in-game
Stop a player from getting up
Timer not stoping
Timer not stoping
Why do people do wait() after while true do instead of while wait() do?
Npc follow and kill
Sword Rewards System - How can I make this more efficient?
How to get the same result as "Tween.Completed:wait()" with "Tween.Completed" function?
Os.date() problem? [SOLVED]
Why is my NPC's pathfinding not working right?
Trigger function each 5 minutes
Sword Rewards System - How can I make this more efficient?
Is there a point to use wait() and why do you use wait?
I really should avoid using wait() or wait(n)
While loop vs Run Service loops
Camera script won't work
If you write wait() without any number in it, how much will it wait?
How to track server uptime
I'm a newbie to scripting, are there any bad habits or good habits I should be building?
Review of a local preload script
Any Tips to Improve my PlayerClass?
Which of these is a better way of counting player minutes?
How to wait untill a variable is true
Need help with some descendant syntax
'IsA' does not work find ImageButton help
While do loops Or Runservice loops...?
How do I create these types of notifications?
Trouble with stopping the money from spamming
Custom wait - the best solution to yielding!
Health script doesnt work
Wait() taking too much time
Make a part spin without timeout
More effective way to do this? (While loops)
Common causes for lengthened wait() time?
While true or renderstepped?
Folder in Backpack is nil?
Revieuw my backpackscript please
How can I prevent zombies in my tower defense game from stopping for half a second at each point they go to?
What is the best method in implement a debounce?
Pointers to reduce lag in your game
Pointers to reduce lag in your game
Attempt to index boolean with 'Changed'
Why does my game crash for this code
RoadNames: a free, easy-to-use road-naming system
Task Library - Now Available!
Task Library - Now Available!
Using delay() VS coroutines?
Why does my game lag?
Why do i keep getting this error in my lighting script?
What loop should I use for my game loop?
How to improve my Interaction Code
Stop using while true do or repeat until to check for changes
How can I fix this Coin GUI Script
A Better Way for ELS Community to Optimize ELS Codes for Performance
Is there a better alternative apart from using wait (1/60)?
How to make gun recoil/Camera shake?
Is it ok to use Roblox wait(n) on the client?
Why is wait() important?
Why is wait() important?
StoryModule | Module for roblox's stories
Is wait() and wait(n) bad practice?
Alternatives to while wait(0) do?
Delay or debris timeout or wait(n)?
Question about while loops
Making Best Wait Testing Script
Stopping Animation using ':Wait'?
Adding speed loop not working
Trouble with datastoring a minutes and hours leaderstats system
DisplayName Exploit Patch (PATCHED BY ROBLOX 8/1/2020)
Unable to reset player's camera
AI Optimization issue
Avoid using while true do & while wait() do!
Any better alternatives to repeat task.wait(.5) until value == true?
Should I use tick() to fix a wait(0.05) loop? (Avoiding wait() and why)
Gui is not working with loading data
game:BindToClose() is not working?
Why is Wait So Bad?
Debris:AddItem() or :Destroy()?
How could I use this table?
Guys i'm trying to loop this script when it go to 1 Transparency it back to 0 Transparency
How would I make a wait() in a while loop?
ClipPhone Updates (Current: ClipPhone v1.0 - Squirrel)
Making my timer using RunService.Heartbeat:Wait()
Need Help Scripting Pushing a Button and T.V "show" starts
Is wait() and wait(n) bad practice?
Need Help Scripting Pushing a Button and T.V "show" starts
Which is better performance wise, wait() or wait(x)?
Roblox Pathfinding Lagging/Stuttering
Why aren't some parts of my character turning invisible?
Need Help Scripting Pushing a Button and T.V "show" starts
Best Game-Round System
Yielding until Motor.DesiredAngle is met
Text(Button/Box/Label).TextBounds to be set upon parent
BindableEvent does not fire
Code not executing with a big wait like wait(512)
Datastore Code Review
While wait/true() VS PropertyChanged?
Problem with millisecond timer / counting time 'while true do' loop in a script
Problem with millisecond timer / counting time 'while true do' loop in a script
DataStore2 causing data loss
Faster Lua VM Released
NumPlayers returns as 0, even when I join the server
Character Teleporting Issues
Remote Event not firing?
Quick question about a CharacterAdded Event?
How to script an Opening GUI?
How do i make a infinite countdown loop
Loop causing lag help
Calculating Total Amount of Players in a Certain Area
How to format meshes into a grid?
GUI only tweens once
"Game script timeout" error
Help with raycasting for melees?
Camera Manipulation Problems
Nothing exists Problem Script
Nothing coming in output with local script
How to end this script with a TextButton?
Why this handler is not working
Pets System: Pet Movment not smooth
Attempt to index local 'PlayerHasWeapon' (a boolean value)
Boolean value script not working

I usually try to avoid wait() unless I’m “waiting in circles” because until now, I didn’t see any other efficient way. This is a really good alternative and I’ll definitely use this in the future. Thanks!

44 Likes

Funny because I raised an issue regarding the use of wait in a Scripting Support thread earlier - mostly the fact that I didn’t want to use it at all. That thread pertained to wait(n) though, not wait without n. I guess this makes up half the answer of what I seek. Convenient timing - hooray.

16 Likes

I almost thought you were going to incentivise not using the actual function entirely at all or even reinventing the wheel.

Until I read the body. But yes I agree people are abusing stuff like this. Especially the one where you poll instead of events. Now polling would be reinventing the wheel.

edit march 2020

hell yeah go ahead and reinvent the wheel here's a better alternative
-- A module script preferably in ReplicatedStorage
local RunService = game:GetService("RunService")

local function wait(n)
    local dt = 0

    while dt < n do
        dt = dt + RunService.Heartbeat:Wait()
    end
    return false, dt
end

return wait
49 Likes

So what I’m getting is that one should avoid loops when possible. I am a large fan of this idea and have to force it on my friend’s scripting.

16 Likes

No, avoid unnecessary loops that an event replicates the behavior of, and never use wait().

22 Likes

While I do agree with your thoughts on wait(), scripts firing BindableEvents to themselves–especially after tightly coupling to the initiating code with an anonymous callback function–well, if the odd, poorly-justified wait() is “code smell”, then such a BindableEvent construct surely is too. BindableEvents are something you generally only want to use for code you don’t want tightly coupled via callbacks or dependency injections, for example to provide communication from closed-source component to software using it, like if you had a slider UI module and you wanted it to generate Changed events that any number of independently-developed bits of code could listen to without the slider having to maintain and iterate over a list of registered callbacks or provide a Lua API interface. BindableEvents in general have a much higher code smell likelihood IMO than wait() statements, since developers use them in some very bad-practice ways, to communicate data between components within their own game that are too difficult to connect properly due to bad code structuring, similar to how global variables are often abused.

31 Likes

*Bookmarked* Great stuff. Thanks for sharing.
Wondering if someone might provide clarification on one point: In an older post about Heartbeat changing to variable freq, zeuxcg says in post 5 that, “…at this point the only way to run logic at fixed frequency is to do a wait() loop.” Do we know if that is still the case?

My understanding is that :Wait() will fire as quickly as it can (in the same variable freq way as the event it’s attached to) while wait() would allow for a loop to be clamped at a slower speed (~ 30 milliseconds, I think). If a fixed/throttled frequency is desired for, let’s say, simplifying your Ai, and wait() turns out to be the right speed, would that be considered a valid production use for it, or is there another way to deal with that? Maybe this is more of a wait(n) example. I’ve been trying to sort out the various use cases for all the clock and wait functions, but it hadn’t yet occurred to me that perhaps wait() had no place at all.

3 Likes

Threads yielded with wait(…) have a chance to resume every other frame, so if you use wait() or wait(0) or wait(n) where n <= 1/30, your thread will resume at most FPS/2 times, or 30Hz if you aren’t dropping frames. So to clarify, it is not a fixed amount of time, everything is tied to frame rate.

22 Likes

Thanks, that’s consistent with what I’ve read. Maybe I’m getting ahead of myself. With other systems I’ve toyed around with, it might have been desirable to clamp an update at 30 ms (which it sounds like wait() would do) for certain things and use a delta in update functions to account for times when even that slower rate wasn’t sufficient. Maybe none of that is relevant to Roblox (I haven’t got around to game Ai yet, and I have no plans to script any physics). So no wait ever? Interesting.

3 Likes

So if I don’t care about micro organization, wait() can still be replaced with Stepped or Heartbeat to be more accurate?

4 Likes

Great post. This pretty much sums it up for me. Event-based programming is far superior when available, and will keep your code running like a finely oiled machine.


If you ever need a continuously-running loop, or even have to do polling for whatever reason, Heartbeat is what I prefer. I use this for polling Gamepad inputs usually, since the event-based inputs for Gamepad are buggy.

21 Likes

So, I’m not finding the post, but I read that using Heartbeat:Wait() (or Stepped, etc) as a loop won’t yield in the way that a wait() loop would. Besides potentially running twice as fast as a wait() loop, are there any practical considerations to note in the event that a slowish script can’t keep up with the Heartbeat? Will it just ignore events that it isn’t ready to handle? [Nevermind, I found the post, and my recollection of it was wrong. A connection to Heartbeat works like you’d expect.]

Maybe I should just run some tests to answer my own goofy questions :grinning:
This thread has help my understanding of a number of things. Wish I could like it more than once. Thanks Kampfkarren and all.

1 Like

If your script can’t keep up with Heartbeat, you’ve got other issues to address. Regardless, I would recommend Connecting to the event, not using :Wait() in a loop:

heartbeat:Connect(function(dt) ... end)

Somewhere in some other thread (Edit: this thread), it was mentioned that this won’t actually spawn a bunch of new threads every heartbeat. It’s smart enough to reuse the same thread. And it’s guaranteed to run every frame.

9 Likes

This is generally what coroutines are used for (coroutine.running, coroutine.yield, coroutine.resume) but they’re a bit finnicky as I mentioned.

What would you recommend instead, it’s at the very least a better solution than wait().

3 Likes

Yes but you’ll still have architectural problems: please don’t just mass replace wait() with Heartbeat:Wait(), it misses the entire point of the post.

3 Likes

The most obvious solution is to have the callback function simply execute the code you need to execute when your flag would have become true (or call another function that does it). Your example is not concrete enough to make it clear why you thing a flag and polling mechanism is required in addition to registering a callback function.

3 Likes

For that small example, yes of course. However I’m referring to situations where that would not be possible (to where an example would be larger and possibly less easy to understand).

5 Likes

Good advice in general. Only instance where I poll is when waiting for my services to load and be exposed (happens only in one script)

2 Likes

What exactly does “waiting for my services to load” mean? Are you using another anti pattern like _G where you can’t yield until it’s added? If not, why can’t your services expose an event that they fire when they load?

2 Likes