How to write efficient code?

Hello developers,

I’ve learned a lot about Roblox game development over the past four years, and I think it’s time to learn how to write efficient code.

However, writing efficient code is so difficult and complex; there seems to be too much to learn (e.g., instance pooling, thread pooling, entity component system).

I’m also not sure how to structure my scripts. There are many different ways to do it.

How can I improve my code efficiency and structure it well?

9 Likes

What I am doing rn is that on the serverside I have a playerController and a gameController, in wich you can call services such as dataService or roundService. I then load the modules with a single serverscript.

On the clientside you would have a module that handles the ui related stuff and effects with remotevents or remotefunctions.

Data is handled using ProfileStore and attributes, for cleanup I use Throve and for signals you could use Signal+ or GoodSignal.

Hope this helped

5 Likes

Most of the code you will ever write doesn’t need to be efficient. Don’t fall into the trap of over optimizing your own code to the point where it becomes unreadable and hard to modify. If anything I think the skill you should develop first is the ability to figure out which segments of code are worth to optimize in the first place, aka which segments of code are responsible for most of the in-game lag. More often than not, lag issues in a game come from very few lines of code that must do a very heavy job, if you really want to optimize, go for those.

I would also recommend against optimizing during the early stages of development, for the same reason I advice against focusing on anti-cheat during early stages. Before you try to make a game work well or be very secure, you first need a game. Something with very defined mechanics and rules. If you focus on matters like this on early development, those mechanics and rules are very likely to change over and over again, which will force you to write the same code over and over again.

19 Likes

ECS is basically functional programming with a shared mutable state.
Nothing mystical. Just tables indexed by entities and pure logic functions acting on them.

Example ECS-ish pattern:

local Players = game:GetService("Players")

local Component_Health:{[Player]:number}={}


local function PlayerAdded(Plr:Player):()
	Component_Health[Plr]=100
	Plr.CharacterAdded:Connect(function(char):()
		local humanoid = char:WaitForChild("Humanoid") :: Humanoid
		Component_Health[Plr]=humanoid.Health
	end)
end

Players.PlayerRemoving:Connect(function(plr):()
	Component_Health[plr]=nil
end)

Players.PlayerAdded:Connect(PlayerAdded)
for i,v in Players:GetPlayers() do PlayerAdded(v) end

Efficient code = code that ONLY does what it needs to do.
No ambiguity. No wrapper circus. No “professional-looking” abstraction slop.

Just:

  • predictable
  • linear
  • optimized logic
  • as few layers as possible between your idea and the bytecode

Don’t drown yourself in unnecessary frameworks. Don’t use React-tier nonsense. Write what you understand, and optimize as you go. Your goal is clean bytecode, not “my architecture looks senior-qualified.”

If you want to understand efficiency, you MUST understand what your Luau turns into. Here’s my bytecode tutorial:

Also: avoid the “single script architecture” and avoid fragmentation for no reason. Luau has no linker - don’t spam ModuleScripts unless they serve a clear purpose.

4 Likes

See, the issue is already showing, OP asked how to swim, and you immediately tossed them into the ocean of abstractions with concrete shoes on.

6 Likes

I thought he wanted to know how he should srructure his scripts …
(and with 4 years of experience I don’t think the OP has a problem understanding with what I wrote)

4 Likes

I can understand what he is saying. Don’t blame him; that’s a good feedback.

6 Likes

Okay, then how I should structure architectures like? Could you give me an example?

1 Like

If your logic is singular, keep it in a singular script.
Splitting it for no reason only scatters functions across the VM, forcing it to switch context unnecessarily. Unlike compiled languages like C++, Luau can’t precompile a ModuleScript and a Script into one function, so pointless splits hurt performance.
Keep related logic unified, ModuleScripts only exist to solve a real problem, not to flex abstractions.

1 Like

I understand it to some extent, but it doesn’t quite resonate with me; could you show me the architecture of your game? I think it would be motivating to see a good example.

Your primary goal should be: How can I achieve my goal in solving X problem and releasing my game?, instead of How to write "efficient code".

There are going to be people who will try to drag you into the rabbit hole of micro-optimization that doesn’t matter in 99.99% of cases in production runtime.

You don’t have to optimize systems that are not restricted by performance bottlenecks, and it’s almost all the code you’ll write.

You don’t have to fight for 2 nanoseconds of execution time, you don’t have to fight for 1 megabyte memory usage. Your code WOULDN’T blow up if you wouldn’t be on your toes 100% of the work time, being careful about not writing “bad” code.

Just write the code that solves your use case, and do not listen to people who will judge you for wasting 0.1 nanosecond of CPU time.

A released game with “inefficient” code is better than a game stuck in development hell, but has “very well optimized bytecode mega super pro 9000”

3 Likes

You should stop trolling and showing people wrong ECS examples, and instead link actual resources that might help people understand ECS and get into it. Saving data into a table doesn’t equal ECS.

Some ACTUAL ECS resources, made by one of the Anime Vanguards developers:

4 Likes

That’s not really how it works. In a dynamic language without a preprocessor or linker, this is the only way to implement ECS without fighting the VM. You’d be better off understanding why I made these decisions instead of trying to argue with me.

Those examples you showed? Just wrappers and metatable circus code all over again. I ran benchmarks and studied the bytecode to see what the VM actually executes.

Cut the fluff, eat the bug :raised_fist: Glory to the New Luau order

2 Likes

We will patiently wait for you to write a better ECS library without “wrappers and metatable circus”, it’s going to be an immense contribution to the developing community.

Show us your might Yarik, I believe in you :raised_fist:

2 Likes

I am a huge believer in non-optimized scripting when building a program, a more “get it rolling and refine” kind of approach. Getting your programs to work this way is a blessing later when you come back and optimize them during refinement. Now you know your concept works, and every refinement just adds to how well it will perform.

Starting out this way tends to come with drawbacks and debugging nightmares. This may just be me, but I like to know that what I’m working on works before I start refining.

As for the real answer to your question, that comes with time, as all things programming do.

2 Likes

You did do the same though… I am not sure how knowledgeable OP is with scripting, but if he is a beginner, he shouldn’t touch type checking

2 Likes

probably 4 years bro

1 Like

ignoring ECS, you should also mention that writing with a consistent format helps with maintaining the efficient code

local Players = game:GetService("Players")

local Component_Health: {[Player]: number} = {} -- Add spacing. This isn't one word.

-- I use PascalCase and avoid abbreviations; Plr -> Player.
local function PlayerAdded(Player: Player): ()
  -- Stating the function returns nothing isn't necessary here (default), 
  -- but i'll leave it.
  Component_Health[Player] = 100

  Player.CharacterAdded:Connect(function(Character): ()
    local Humanoid = Character:WaitForChild("Humanoid") :: Humanoid
    Component_Health[Player] = Humanoid.Health
  end)
end

local function PlayerRemoving(Player: Player): ()
  Component_Health[Player] = nil
end

Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerRemoving)

-- Index isn't used; v -> Player.
-- I like one-liners, but don't force it.
for _, Player in (Players:GetPlayers()) do 
  PlayerAdded(Player) 
end

note: i’m really just nitpicking because the original code block was cheaping out on spacing. find a good format you like and stick with it (everywhere)

2 Likes

Many beginners have a problem with “functions overhead” this is when one function calls another function and this another function calls another function. this problem is partially covered with inline calculations,
for example:

-- BAD EXAMPLE:

-- ye snake_case cuz ima rust programmer also
local function calculate_damage(atack: number, defense: number) 
      return atack * 2 - defense
end

local total_damage = 0
for i = 1, 100_000 do
   total_damage = total_damage + calculate_damage(4, 2)
end

print(total_damage) -- <- bad perfomance
-- GOOD EXAMPLE WITH `INLINE` calculations

local total_damage = 0
for i = 1, 100_000 do
   total_damage = total_damage + (10 * 2 - 5) -- <- GOOD OPTIMIZATION
end

The point of inline calculations is to remove unnecessary intermediaries in the form of functions

4 Likes

There isn’t an “ECS library.”
ECS is a paradigm - a way to structure data and logic.
What you’re calling “ECS libraries” are just wrappers stacked on wrappers, and every wrapper is overhead.

Luau doesn’t have macros, a linker, or compile-time specialization like C/C++, so a true zero-cost ECS cannot exist as a library.
The moment you abstract it, you’re already losing the whole point.

If you want real ECS, you write it directly.
If you want wrappers… that’s just OOP with a funny hat.

2 Likes