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.

471 Likes
Is there a way to write whole game codebases without the use of arbitrary waits?
(problem) wait() not being 100% accurate
2D Player Movement
Wait() taking absurd amounts of time
Golang's defer or similar function
Are loops really that scary?
One time Key Down
How intensive is tick()?
Polygon Triangulation
Best Way to Wait for Condition or Time
Should I ever use wait() function?
Is this method of yielding any better than wait()?
Making a round timer without using wait?
Delay or debris timeout or wait(n)?
Loading intro not working?
What is the most efficient way to check if (condition) has happened?
Tick() or wait() ? Wait for minutes?
Game script timeout
Yielding until Motor.DesiredAngle is met
Wait() Based on Framerate
Scriptable Camera Issue
Rainbow Items Script From client
Can someone explain all the events of run service in detail?
How to wait till a bollean value is true?
Need Help understanding RunService
Which method is the best performance-wise?
StoryModule | Module for roblox's stories
Loading Scripts and Remote Event Timing Question
Is wait() and wait(n) bad practice?
Is wait() and wait(n) bad practice?
Alternatives to while wait(0) do?
How do you make a projectile go into the direction an object is facing?
How can I improve my script's efficiency? sometimes it has damage delays
Remote Event not firing?
How to wait for a function return?
Elegant way to set match length to 180 seconds
Add All Values not working
Best method to keep track of time for in-game timers?
Why is my loop not working correctly?
How to don't wait to function to end?
Effective Server-sided cooldown
[CLOSED MADE A MISTAKE!] Save Memory Project [ALPHA 1.1]
Script Keeps Restarting when a Click Event Occurs
Script Keeps Restarting when a Click Event Occurs
Issue with Camera rotating around a focus point
_ is not a valid member of model when it clearly is
Weapon server lag
Which is better performance wise, wait() or wait(x)?
Roblox Pathfinding Lagging/Stuttering
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
MoveTo In repeat loops, inefficient?
How would I make it so that anyone that survives the round gets a win, not just the last person alive?
How to make an ImageLabel keep rotating around?
Lookvector change
DisplayName Exploit Patch (PATCHED BY ROBLOX 8/1/2020)
How to script an Opening GUI?
Is there a more efficient way of writing this?
Using while loops as modified waits?
Pathfinding bot freezes when it approaches its target
Whille wait() do loops causing lag
Waits in coroutines are stopping the whole script
Change the speed of all players
Anti-Exploit Not Working
While true do loop speeds up
How can I make it so the player can't move the camera angle?
Combat not working after death
Tracking time in Leaderstats
Instant Regeneration Problem?
How much can roblox as an engine handle?
Script doesn't work
Ways for multiple player location checks without polling
Why is it that when the Player's Character touches the ground, it gives delayed signals to clients? Please help
Invite Friends [OPEN SROUCE]
Wait function alternative?
Script Help Please
Problems with shift to sprint script
Third Person Camera Script
How to Squash "ghost" Bugs
Could I have some examples of BindableEvents and BindableFunctions
While wait/true() VS PropertyChanged?
Attempt to compare number and string
How do you make a part vibrate?
How to make my compass show direction to the part?
Gui disappear script not working
Nothing exists Problem Script
Teleporting part to Local Player not working?
'Transferring' text from a TextBox to a TextLabel
Setting part CFrame rotation lagging
Is it good to use repeat wait() loops for this? Is there a more efficient method?
How to make a Motor turn smoothly
Best Scripting Practices?
One second delay after timer countdown ends
Bug in my script for showing a view from a camera until the play button is pressed
Developing an Anti Exploit script. Would love corrections
Loading Screen won't show up
How can I make my CFrame movement code non-hardcoded?
Loops executing function incorrect amount of times
How do I make this Part's CFrame Always be equal to the Players CFrame?
In-Game System Messages
How do I make this car spawner script have a cooldown?
Unable To Get Song Name
Will this cause lag?
Need Help Scripting Pushing a Button and T.V "show" starts
Need Help Scripting Pushing a Button and T.V "show" starts
Roblox game crashes for mobile users?
Need Help Scripting Pushing a Button and T.V "show" starts
Boolean value script not working
Nothing coming in output with local script
Why this handler is not working
Attempt to index local 'PlayerHasWeapon' (a boolean value)
How do i make a infinite countdown loop
Pets System: Pet Movment not smooth
How to end this script with a TextButton?
For i,v loop not firing properly
Camera Manipulation Problems
Help with raycasting for melees?
GUI only tweens once
DataStore2 causing data loss
Os.date() problem? [SOLVED]
How to format meshes into a grid?
Calculating Total Amount of Players in a Certain Area
Loop causing lag help
Best Game-Round System
Datastore Code Review
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
Quick question about a CharacterAdded Event?
Character Teleporting Issues
NumPlayers returns as 0, even when I join the server
Faster Lua VM Released
Text(Button/Box/Label).TextBounds to be set upon parent
"Game script timeout" error
Camera script won't work
Enhancing Projectile?
How to track server uptime
Should I use tick() to fix a wait(0.05) loop? (Avoiding wait() and why)
Is there a point to use wait() and why do you use wait?
I really should avoid using wait() or wait(n)
Is repeat wait() bad?
Trigger function each 5 minutes
[VIDEO] How to not use WHILE WAIT() loops
Custom Face Help
Sword Rewards System - How can I make this more efficient?
Tool assigning script works in studio but not in-game
How do you cancel a for loop immediately?
How do you cancel a for loop immediately?
Game's performance decreases overtime
Sword Rewards System - How can I make this more efficient?
Making my timer using RunService.Heartbeat:Wait()
Timer not stoping
Timer not stoping
Stop a player from getting up
UserHasBadgeAsync Working But Occuring An Error
Rainbow Items Script From client
Rainbow Items Script From client

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!

27 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.

8 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
25 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.

10 Likes

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

10 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.

14 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.

1 Like

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.

15 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.

2 Likes

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

2 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.

12 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.

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.

7 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().

2 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.

1 Like

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)

1 Like

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