The While-Wait-Do Idiom, by cntkillme: Addressing while wait() loops

EDIT (08/09/2021): Yes, I’ve actually started seeing while task.wait(). Just so it’s clear, the document does cover yielding functions being used as conditions as well. task.wait() is not a better replacement and also falls under discouraged practice as per the below.


Introduction

Earlier today on my usual camp-out by the Scripting Support category, I stumbled across a piece of code that set off my nitpick ringer and reminded me of a document I stumbled across long ago. Today, after being prompted about the matter, I went and dug up said document. Figured I’d share it now.

The document in question is entitled While-Wait-Do Idiom, written by cntkillme. It discuss the practice of using wait(n) in the conditional field of a while loop, or something that contains wait(n) (note that this is also applicable for wait without n, as leaving n unspecified will use the minimum value as a default).

-- The While-Wait-Do Idiom in code, through different examples

-- 1
while wait() do
    print("foo bar")
end

-- 2
while wait(1) do
    if aMagnitude < bMagnitude then
        return cSomethingProbably
    end
end

-- 3
local function foo(bar)
    wait(0.5)
    return foobar
end

while foo(bar2) do
    print("foo bar?")
end

I can’t exactly remember how I was introduced to the document, though reading this has influenced and changed how I write while loops. I believe this person also authored more documents regarding practices by Roblox developers, however I unfortunately do not have those nor know where to access them, if they exist.

This is a years-old document from my understanding, but I still see the relevance of some of the points brought up and statements made in this document. I still see while wait being used even now. I also figure that I might as well not let something interesting rot in my document history, so here it is.


What you can expect

There are a few key points to take away from this document, through my own reading. The primary point is that the author disagrees with the use of this idiom and that one should avoid while wait do, or cut it out from codebase altogether.

Here’s about a brief rundown of what you can expect:

  • An introduction to the While-Wait-Idiom
  • The problems that said idiom may cause
  • Three addressed points regarding the idiom and it’s supposed “benefits”:
    • “Less code is cleaner [or more readable] code”
    • “Less code is faster code”
    • “Everyone uses it”
  • An explanation of the problem of this idiom in terms of maintainability
  • The bad habits this teaches (improper usage of the conditional statement)
  • A tl;dr with nothing resourceful (don’t skip to it, seriously) - try reading the whole document, since the tl;dr doesn’t include any useful information aside from telling you to go read the document. It’s just 3 and a little bit of a 4th page of good knowledge.

It’s up to you, now

I’d hate to take away from the document by summarising it myself, so I would personally encourage you to read it if you take any interest. It would probably do a better job of explaining it than I ever could anyway.

I’ve outlined what you can expect to read up on through the document, so the rest is up to you.

I have personally found this document very helpful. I’m in the progress of changing a lot of my coding practices and habits - this document was one such contribution to that change.

100 Likes
Help with 'while wait'
How to prevent client from abusing RemoteEvent to get infinite cash
Best way to loop from server
My Game just Freezes When Making a New Server
Why is the kicking part firing way too many times even though it's not supposed to?
Data isn't saving at timed intervals and upon player leaving
Advice on the best way to use Magnitude for a terminal
Mouse Dont Print Target Name how fix?
Trying to censor TextBox text?
Don't seem to be able to print from Script in ServerServices
Help with an ongoing problem with Streaming enabled?
Having problems with DataStore
Overhead GUI Rank system
What is this dialogue script's problem?
[HELP!] Game Timeout!
How to keep track of time played?
#players script not working
What are expensive things to do in a Roblox game, that could impact performance?
Optimizing Particle Emitter
How would you go about making a custom player list with a leaderstat such as level next to the player
Guessing Game of sorts?
Creating a script that crashes the player on touch
Error trying to put 3 players on a team
Walkspeed counter
Scripting optimisation questions
What's the best way to make a multi-hit killbrick that works with CanCollide set to true and false?
Leaderstats badge not working? Help
Character follow mouse not working as intended?
Better way to register where to place pixels when mouse down
C only works when not chatting?
Part that changes color when you click on it
.Magnitude question
Need help with a blue Flag script for a racing game that tells a player he is being lapped
Countdown script is not working?
Random theme doesn't work
Os.date() problem? [SOLVED]
How can I fix my rainbow above-head-GUI script?
"Game script timeout" error
My DataStore randomly fails why? (New Developer)
Stopping the player from resetting within a tool
Countdown is not exact
How do i make a countdown
How to end this script with a TextButton?
How do you make a fading intro text GUI?
How do I repeat this script?
Issue with XP not saving
Something wrong with DataStore
How to use Tweening for day/night cycle
Custom daylight cycle
How to make a secret finding compass
Money gui doesn't working
How do you make a part vibrate?
How do games have custom backpack GUIs?
Using UserInputService to make an 'E-To-Open Door' function (All platforms supported.)
Why will it only follow one player and attack one player?
Preventing/Detecting Metatable Hooking?
Remote Function Returning Nil
Random spawn for coin not working
Why am I getting error: 20:59:51.467 - Workspace.Script:13: attempt to index function with 'Destroy'
Script Timeout Error?
DisplayName Exploit Patch (PATCHED BY ROBLOX 8/1/2020)
How to make a loop that never ends?
Why does my output error a working script?
Introducing colbert2677, Hobbyist Developer and Platform Enthusiast
Memory is through the roof. What should I change?
Code is changing the time of something too slow when it's just right
Need Help understanding RunService
Changing Decal for Cafe Menu
How to create an auto cash giver
What's the Best Wait Time in a While Loop for Best Preformance?
[CLOSED MADE A MISTAKE!] Save Memory Project [ALPHA 1.1]
What's the Best Wait Time in a While Loop for Best Preformance?
While loop breaks after calling function
Ideas for anti-exploit
Need help scripting a moving bus system
How could I improve my zombie script?
Will making an enemy move to the player with this method cause a lot of lag?
Zone+ v1 (deprecated) | Retrieving players within an area/zone
Spawn function doesn't run loop
Os.date() problem? [SOLVED]
Os.date() problem? [SOLVED]
TweenService bug
Which one is less efficient between while wait() do and while true do wait()
Region3 with whitelist not detecting players
Things to look out for when using Network Events (RenderStepped, Stepped, Heartbeat)
How can I lower or stop raycast lag?
KeepUpdate module
Help with math.random()
Broken Daily Reward Timer
Why is wait() important?
Event every time os.time changes?
TextButton.Activated event only firing once
Alternatives to while wait(0) do?
NPC Spawner is spawning only 1 instead of looping
RunningService.HeartBeat:Connect() vs while task.wait() do
Why isn’t math.random not random?
Should I limit the amount of while true loops I put in my game?
How to make a group of players to another game
Avoid using while true do & while wait() do!
[SOLVED] game script timout
Ui visible when humanoid is idle help
Roblox Anti-Exploit Question
Is Roblox not processing my code?
Thoughts on my pet script?
While loop not running even debugged

Good read, thanks for the recommendation!

9 Likes

I’ve read this one before, and it was relatively fun to read, although I don’t really agree with it. Considering this idiom has already became so popular that it’s not hard to read code which uses it.
In the end, people will (and should imo) do things however they like, as long as it’s not group project or something someone else will maybe have to debug later on.

4 Likes

Personal Summary

I think I have already broken out of the cycle. I found my own modus operandi in programming, some parts of it are inconsistent due to low experience within the area. Literally I write as fast and messy as possible first before finding shortcuts and simplifications of the code, for reasons of easier editing… wait a minute, am I still stuck in the cycle within a cycle?

Well-done document though, not tasty, just wordy enough to be read properly.

Group projects with another scripter is quite a hassle sometimes. Your writing methods may be vastly different between you and the other scripter. You simply have to work it out, clearly. Even if they have a different writing quirk, always write to your own conventions – whatever for your eyes.

I should really question myself about my coding habits.

4 Likes

While this looks like a neat shortcut at first, I really dislike this pattern. A lot.

The main reason I dislike this pattern is because “it relies on the fact that wait always returns a truth-y value” which is very unintuitive. To write good code is to write intelligible and intuitive code. Someday your future self (or someone else) will read your code again and you’ll want that code to be an easy read.

Another point I take issue with this code is that it shadows a very important factor when suspending your code: wait(t) is not guaranteed to wait for t!

Function wait will return two values. The first return value (the delta time the thread was suspended) is super important for loops.

Take for example, a counter that pulses roughly once per second.
If you were to use this pattern, your code could look something like this:

local sum = 0
while wait(1) do
    sum = sum + 1
    -- ...
end

This will not work as you expect! Because wait(1) is not guaranteed to wait exactly one second, your counter will start to drift as some frames take a little bit more than a second (I currently cannot find any good documentation or write-ups explaining the importance of delta time in loops, but if/when I find one, it will go here)

Let’s refactor this code so it works as expected:

local sum = 0
while true do
    local deltaTime = wait(1)
    sum = sum + deltaTime
end

Here we have a very similar clock, but now we are able to retrieve and use the actual wait time within the loop. This is a very simple example, but using delta time instead of a “fixed time step” is useful when you want to create smooth animations or sync your game code to time in real life.


extra

Honestly though, when writing loops I find its usually in my best interests to bind to one of the RunService methods depending on my current needs. while and repeat loops are useful when comparing against an actual conditional statement for low-level functions.

Getting into how/when to use RunService events is beyond the scope of this forum post.

24 Likes

I would say using while wait(n) do is your habit, but using while true/conditions do is a good habit, there are few advantages of using while true/conditions do over while wait(n) do

Add more codes easily
For example: Counting the time the player have used and show on the textlabel.

--Orignal Code you have
while wait() do

end

--Transformed
local TimeUsed = 0
while true do
    TimeUsed = TimeUsed + wait()
    TextLabel.Text = TimeUsed 
end

Logical problem
Actually this is not a really big problem in the script.
Just like in the script you sent, there is a logical problem. Try to found the difference between Script A and Script B.

--Script A
while wait(1) do
    if aMagnitude < bMagnitude then
        return cSomethingProbably
    end
    --Do something 
end

--Script B
while aMagnitude < bMagnitude do
    wait(1)
    --Do something 
end
return cSomethingProbably

The flow of Script A is wait(1) > If(aMagnitude < bMagnitude) > Do something (Assume aMagnitude < bMagnitude now) > wait(1) > If(aMagnitude < bMagnitude) > return

The flow of Script B is If(aMagnitude < bMagnitude) > wait(1) > Do something (Assume aMagnitude < bMagnitude now) > If(aMagnitude < bMagnitude) > return

Assume aMagnitude < bMagnitude at the start, wait(1) will still happen in Script A but not Script B, that means you script need at least one more sec to run.

Now you can find the problem of using while wait() do, not everyone found this problem and this problem is not easily noticed.

7 Likes

Most of the time when I do while wait() do I literally just do it to rough out code with. The only thing is that I tend to not go back and clean up after myself.

Reading over both the document and this thread - especially with @GollyGreg mentioning truthiness, and the reminder that the conditional part of a while loop exists - makes me wonder what else I’m doing that is a bad coding practice in the first place.

2 Likes

From when I had an opportunity to talk with him. CntKillMe was a big part of the Scripting Helpers group on Roblox which helps scripters out with code and such. I’m unsure if he’s still a part of that community. But you could possibly find him there if you wish to ask about more documents. I’ve since left the community so, I’m no longer in contact with him.

2 Likes

I don’t typically worry about wait() returning a non truthy value since it would be reasonable to assume that it will never return nil or false in Roblox (it returns the actual yield time). Drifting issues with wait aren’t super important for most games either, unless precision is needed I’d say its a trivial issue that wouldn’t affect gameplay. It’s definitely better to not have it, but it’s likely not a massive deal for most people anyway.

There was a take someone did a while back where using wait with no arguments is a code smell, could probably relate to this post in some way.

4 Likes

Isn’t zero a truthy value? I thought that the only falsey values were nil and false.

1 Like

Double checked, and it is. Probably was thinking of another language, thanks!

edit: Edited post to reflect this

2 Likes

The concern I expressed is not that it could return a false value for the statement (because it cannot), but rather it obscures the fact that it is relying on the wait to return a number which what gives it a code smell.

2 Likes

Thanks for the read. It’s annoying how much code I find that uses wait() when better things can be done, such as using RunService.

Since I learned wait always returns a true-validating value, I’m getting away from while wait(someDuration) dos.

The only reasons I had for using the pattern at all was in case the script was disabled, the wait loop would stop due to wait making an odd return, which was my myth. And it also was for forever-running effects that need to run at a low frequency, such as forever animating a lot of bricks at once server-side, or forever periodically checking whether a certain condition is true before making a game event happen.

For forever-running high-frequency loops, I use RunService instead. Like this:

local RunService = game:GetService("RunService")
RunService.RenderStepped:Connect(function(deltaTime)
    -- Do something.
end)

If I find myself running wait() many times, I would prefer using a RunService event instead. Like this:

local RunService = game:GetService("RunService")
for i = 1, 60 do
    RunService.Stepped:Wait()
    -- Do something.
end

For longer waits, it’s better to use patterns like this:

for i = 1, 10 do
    wait(0.5)
    -- Do something.
end

Or this:

local x, y = 0, 10
while x < y do
    local deltaTime = wait(1)
    x = x + deltaTime
    -- Do something.
end

Hopefully you get the idea.

4 Likes

It’s implicitly passing the “while” condition. It’s bad code. Write clean and clear code.

1 Like

I stopped using While loops a while ago, and just use repeats, but I was wondering if the same applies to repeats? This is usually how I do my repeats:

repeat wait()
if exampleA == exampleB then
end
until exampleC == 0 

That’s fine, because the loop condition is readable/clear/explicit. It’s not technically formatted correctly, since the ‘wait’ should be on the next line, but that really doesn’t matter.

Disclaimer: Formatting does matter, but sometimes it makes sense to break standard formatting for the sake of making your code look better

3 Likes

This article is extremely subjective. This is more of an opinion rather than good practice.
On a readability level, one could make the argument that if the wait needs to be at the start, it can stay in the condition, otherwise, it can be moved. Not only that, but the article goes against using break to close a while loop. Break is a blessing, because it gives the programmer the ability to have a similar behaviour to return inside loops, and should be used as often as possible to make your code as familiar to other programmers (Or yourself in 2 months) as possible.

The argument on performance is ridiculous, there is no significant loss in performance unless you are literally running it 200 times per frame, in which case you would be using a RunService loop.

2 Likes

Did you read the article? It explains exactly why this idiom is not a good practice. One of the reasons being it is misleading as it implies the result of wait is important and relevant to the actual logic being run in the body of the control structure when it really isn’t. It is abusing the fact that wait returns a truthy value. I have asked some users in discord servers if they knew how it works, and surprisingly around 95%+ didn’t know how it worked.

The article even says so itself.

image

:\

4 Likes

It’s always good to give things a thorough read-through and deeply understand the concepts it’s putting forth before challenging it. This isn’t really an opinion as it is based on actual behaviours of the engine and you aren’t forced to adhere to what it puts forth either.

The main point is the implications that are made and missed by using the function wait in the condition of a while loop, which is thoroughly explained in point one (and which is seemingly not understood). If you really need a wait at the start of each iteration, you can still do so without using it as the condition.

The article does not make any explicit condemnations of break. Break is only used in a code sample showing what programmers may typically end up with by not using the conditional properly versus using it properly. In the first example, the while loop is terminated with break when the condition is met, but in the second example the while terminates itself when the condition is met without the use of break. It does not, at all, discourage the use of break.

Break is still very important in both terminating and non-terminating loops, as is continue, if you are performing iterations where you have cases in which you need to prematurely skip an iteration or completely end the loop altogether. That’s a different topic as well as irrelevant to what this article is trying to convey. If you don’t need to prematurely end loops, then you won’t often use break. Using the conditional properly will appropriately end the loop when it needs to. You should not “use break as often as possible” to “make your code as familiar to other programmers as possible”.

Lastly, the topic of performance: the article states that this is a negligible concern, which it is. It doesn’t say that the performance is affected by a large margin. The performance difference doesn’t change between using wait in the conditional or not, the bottleneck there is the task scheduler and when it resumes the thread to start the next iteration. I don’t know much about the technical details so you should read the article again. Performance in this case isn’t really worth thinking about, it’s more about the practice and implications of using wait in the conditional.

4 Likes

Believe it or not,
while true do wait() end

and

while wait() do end

actually have a slight difference in timing, same with

while foo==true do wait() end

1 Like