FindFirstChild and WaitForChild: Addressing common bad practices and habits of each

Huh. I might be starting to understand your point of disagreement, though I’m still not quite following. I may require further explanation or a rundown in a DM.

  • Considering I mentioned two things, I’m unsure of which one you’re talking about specifically.

  • Code failure potential was addressed in the thread itself. Was there something I said that contradicted it? I’m thinking that it’s based on what I said about replication and child availability and how that matter is incorrect, but I don’t fully follow.

  • Which undocumented behaviour did I list?

Nonetheless, I’ll try finding a way to incorporate these points (changes on replication) into the thread where appropriate, since this isn’t quite a resource on replication though the stance is relevant.

3 Likes

The child replication for objects under a tool is not officially documented in any way.

As for replication, I know for a fact that replication times vary depending on game performance, and that the behavior your refered is not always factual.

2 Likes

Hm… I’ll be sure to have a look and investigate. The point has been made clear to me now, it seems; thank you for elaborating.

It definitely is an oversight to have mentioned any assertions about replication without taking in the process and the factors that affect them. I’ll have to do some tests under light and heavy load environments and look up what I can.

4 Likes

Loved this thread!

Personally, I would never use :WaitForChild() especially with the new upcoming VM.
This is because for example, if your waiting for the root of a player’s character, properties like the CFrame are not loaded yet and if your teleporting the player when waiting for the root, it will error because the CFrame hasn’t even loaded.

I do use :FindFirstChild() wherever I can though, I do this because it prevents me from using a pcall if it errors when I index it directly, instead it can return a Boolean if it even exists. How lovely it is right?

8 Likes

The behaviour you’re reporting is likely a bug, but I’ll certainly do some tests to confirm it.

2 Likes

Addressing an earlier comment I made. Thanks for all the feedback up to now, I’m still in the process of testing around. Seems I have some misplaced information and new information.

There’s still a lot of threads out there on FindFirstChild and WaitForChild. This one is essentially a compilation of my own knowledge, information and testing. Still have a ways to go.


Added a bad habit for WaitForChild, 4. This one relates to the attempt of microoptimisation by indirectly using the DataModel’s WaitForChild (inherited from Instance), which is often confused for a regular optimisation. In short: you save barely anything noticeable, may miss bad uses of either function and may potentially affect readability.


After some lengthy discussion with @devSparkle above, I’m inclined to review the post as well as a few responses I’ve offered and correct them as such. I’d also like to find a better format for the thread if at all possible. I’ll see what I can manage soon.


This post:

No good. Dot syntax can be used on contents of ReplicatedStorage. This is because ReplicatedStorage’s contents are among objects that are replicated before DataModel.Loaded fires, which is when non-ReplicatedFirst scripts run.

There’s also a pretty good post you can reference that talks about avoiding WaitForChild by waiting for the game to load, even though LocalScripts don’t fire until that’s done anyway. Essentially: implicit (or more properly, static) objects can avoid the use of WFC, but instances added at run time (or more properly, dynamic objects) should incorporate WFC. See that here:

@Dandystan gave me this pointer.1

cc @superhudhayfa


1: But doesn’t want to post.

11 Likes

To sum it up nicely: Instance:WaitForChild is only necessary when accessing
(assuming Workspace.StreamingEnabled is disabled*):

  • Instances parented during runtime (including StarterGui to PlayerGui cloning).
  • Descendants of instances replicated to the client during runtime (including StarterGui to PlayerGui cloning).
  • Instances outside of ReplicatedFirst from a LocalScript inside ReplicatedFirst before DataModel.Loaded fires/DataModel:IsLoaded returns true.
  • If network streaming is enabled, BaseParts or descendants of BaseParts inside Workspace.

*Regarding network streaming’s effect on replication behaviour: I believe, when it is enabled, WaitForChild is also necessary when accessing descendants of Workspace. I am not 100% sure if anything else is affected.

6 Likes

As far as StreamingEnabled goes, WaitForChild is required for BasePart descendants of the Workspace only, since streaming pertains mostly to rendering. Parts streamed out effectively do not exist.

Thanks for the feedback though! I’ll be sure to weave something in for a future edit.

2 Likes

Hey there! I have a question, do you need to use WaitForChild on ReplicatedStorage from local scripts for instances you explicitly place there from studio, also, if you need to use it, can you use game.Loaded to allow direct access.

2 Likes

I think it was mentioned that the contents of ReplicatedStorage are replicated before LocalScripts execute and game.Loaded fires, so just indexing directly shouldn’t be a problem.

That is if you explicitly create the objects in Studio (for them to exist implicitly on the server), not the case for late instancing.

My source: @colbert2677

Provided the instance you’re trying to access exists there with the premade part of the game (the stuff you construct in Studio) and isn’t placed there by a script using Instance.new or anything, yes it’s okay to use dot syntax on the client.

1 Like

I pm’ed @Anaminus and he said that you can’t, base on your reply you made to another thread: Can I rely on ReplicatedStorage Instances loading before PlayerScripts run?, but maybe I asked the question incorrectly, If anyone else has an answer I would love to get your input.

1 Like

Here is the question I sent:
“Hi just clarifying that your post is saying that you can directly access static instances in replicated storage from a local script, aka you don’t have to use WaitForChild.”
Reply:
" That would be false. Objects visible to a LocalScript will always be on the client. Objects under ReplicatedStorage on the client will never be static, because they have to be replicated in from the server.

The only way you could access an object under ReplicatedStorage directly is if the object exists on the server before the client connects, and the LocalScript is guarded by game.Loaded ."

1 Like

The following thread, which was written by an engineer, better explains this dynamic in detail. Best practices around FindFirstChild and WaitForChild can be answered by this thread, but I wrote one that directly addresses those habits in respect to the DataModel.

LocalScripts do not need to use WaitForChild on instances in ReplicatedStorage that you explicitly place in from Studio, but it will need to be for objects that either the server or client creates during a running server (runtime). game.Loaded will not need to be used here since most LocalScripts are game.Loaded protected anyway (they do not execute until this fires).

I’m still doing replication tests myself and finding a time to polish the thread since it has a few inaccuracies and information to be covered, but the answer you got back is right. The server needs to replicate instances not created by the client that are eligible for replication. Premade parts in Studio will always be guaranteed available before a client connects. Most LocalScripts other than ones in ReplicatedFirst are protected from running until game.Loaded fires.

7 Likes

The post where I talk about static vs dynamic instances tries to figure out when it’s safe to use direct indexing. This was really a mistake; it is never safe to use direct indexing, though for reasons unrelated to replication. The following post has more detail:

It was also a mistake because it answers a misguided question. The question was “when is it safe to use direct indexing?”, and the answer is “when the instance exists”. The actual underlying question is “when does the instance exist?”, and the answer to that is “it’s complicated”.

Making these “static” and “dynamic” labels was also a mistake. We can’t take an abstracted approach to instance lifetime. The game tree must be understood as a dynamic data structure with instances being added and removed at any point in time.

There are several contexts in which instances will be present, or not exist. For example,

  • When studio opens a place file, the game tree will contain all the instances that were loaded from the file.
  • A server starts in much the same way, but is more dynamic because the game is actually running.
  • When a client starts, the game tree will contain basically nothing, because it has not yet received instances replicated from the server.
  • Certain instances will never be replicated over, depending on where they are in the tree.

There are a number of behaviors that will add and remove instances at particular points in time. Examples:

  • Replication will add and remove instances from a client’s game tree as needed to match that of the server.
  • When a client initially connects, one large snapshot of the server’s game tree is replicated over.
  • Instances can be created, destroyed, and moved by scripts, which themselves can be added and moved around.

It’s important to identify what behaviors are possible in a given context. Such as,

  • Replication does not occur in a non-running studio session.
  • Scripts in the game tree are also not running at this point.
  • On the other hand, plugins are running.
  • On the client, CoreScripts can start running before anything has even started replicating.
  • StarterScripts wont start running until the client has finished loading (game.Loaded).

Finally, there are a variety of APIs that are able to make certain guarantees about instances. To name a few:

  • Given a correct name, game:GetService() will always return the service corresponding to the name.
  • workspace.Terrain will always exist (“Terrain” is also a property of Workspace).
  • ChildAdded will always pass a newly added child.
  • Players.PlayerAdded will always pass a child that will be of the Player class.
  • If Player.Character is not nil, it is guaranteed to point to the player’s character.
  • FindFirstChild will return the first child instance of the given name, or nil if it does not exist.
  • Instance.new has the characteristic that the returned instance has absolutely no other references to it, so it is actually “safe” to use. A cloned instance tree also has this characteristic.
  • A reference to an instance stored in a variable or table will stick around. Don’t discard the reference if you’ll be using the instance later.

Such guarantees should be utilized as much as possible, because they do most of the work of locating an instance in a dynamic structure. If such an API does not exist, it is important to make (or find) a feature request for it, and rally support. It seems more clear now than ever that developers need some sort of API that is as convenient as direct indexing, but also as robust as FindFirstChild. Proposals for such have been made in the past, so they should be revisited.

23 Likes

A useful addendum to what @Anaminus has already outlined is: never trust direct indexing to do as you wish. In Roblox’s internal indexing hierarchy, properties will always be indexed first. I’m sure this is common knowledge but to this day (somehow) games still attempt to directly index. This is incredibly dangerous for properties you may not control. Take for example my username:

local Players = game:GetSerivce("Players")
local PlayerName = "GameAnnounce"

-- This code is fine, even though you could just fetch player from PlayerAdded
local Player = Players:FindFirstChild(PlayerName)
-- This code is not fine. This returns Roblox's event (https://developer.roblox.com/en-us/api-reference/event/Players/GameAnnounce)
local NotFinePlayer = Players[Player.Name]

-- This will always be false, even if my player is not in your game (RbxEvent == nil)
print(NotFinePlayer == Player) -- > false

A notable, yet unfortunate, example of this is Arsenal. By indexing players, and I assume other members, directly they open themselves up to bugs which they cannot foresee (within reason). I understand that this is a rare example, but a good one nevertheless to illustrate the dangers of direct indexing.

8 Likes

Just going to pop in here as noone has mentioned this, FindFirstChild and all forms of it can cause lag and can slow down your scripts depending on how many you are using.

2 Likes

Though there is a case that developers may miss which is the fact that an object’s properties have higher precedence than a child. In the case that a child instance shares a name with an object property, whether it’s a new or existing property, direct indexing will raise problems in your code.

Well, pretty negligible if you’re good with names though.

3 Likes

Always wondered this, why not just use a period instead of FindFirstChild?

if workspace:FindFirstChild("Part") then

end

sure, you can do that, but why not just do this?

if workspace.Part then

end
1 Like

It will error if the part doesn’t exist.

well if my code stopped working and one of the reasons was that findfirstchild returned false then using

if workspace.Part then

end

and had an error, the output would tell me why the if statement didn’t pass, I mean that seems superior since it actually tells you what screwed up, but that’s just my opinion

1 Like