Debounce is one of the most important patterns in Roblox scripting. Without it, your code can fire multiple times when you only wanted it once. You click a button and damage the enemy five times. You press a key and jump thirty times. Events triggered repeatedly cause chaos.
The good news: debounce is simple once you understand it. You use a boolean flag (a true/false variable) to temporarily block a function from running, then turn it back on after a delay.
The Debounce Pattern
Debounce works in three steps:
- Check if the action is allowed (is the flag true?)
- If yes, run the code and set the flag to false (block it)
- Wait a moment, then set the flag back to true (allow it again)
Here's the simplest example:
local debounce = true
button.Activated:Connect(function()
if debounce then
debounce = false
-- Your code runs here
print("Button pressed!")
wait(1) -- Wait 1 second
debounce = true
end
end)When the player clicks the button:
- If
debounceistrue, the code runs and debounce becomesfalse. - The next click during the 1-second wait will skip the code entirely (debounce check fails).
- After 1 second, debounce turns back to
true, and the button works again.
Debounce in Damage Systems
This is where debounce really shines. If your sword touches an enemy, you only want to deal damage once, not every frame the sword overlaps the enemy.
local debounce = false
local damage = 25
sword.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if humanoid and not debounce then
debounce = true
humanoid:TakeDamage(damage)
print("Hit for " .. damage .. " damage!")
wait(0.5) -- Prevent hitting the same enemy twice in 0.5 seconds
debounce = false
end
end)Without debounce, the Touched event might fire 10+ times per second while the sword is touching the enemy. With debounce, you deal damage only once every 0.5 seconds.
Debounce with Functions
If you have a function you want to debounce, pass the debounce check into it:
local debounce = true
local function useAbility()
print("Ability used!")
-- Your ability code here
end
userInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then return end
if input.KeyCode == Enum.KeyCode.E and debounce then
debounce = false
useAbility()
wait(2) -- 2-second cooldown
debounce = true
end
end)Per-Player Debounce
In some games, you need different debounce timers for each player. A shared debounce flag blocks everyone. Instead, store a debounce state for each player:
local debounces = {} -- Dictionary to store debounce state per player
button.Activated:Connect(function(player)
-- Initialize debounce for this player if not set
if debounces[player] == nil then
debounces[player] = true
end
if debounces[player] then
debounces[player] = false
print(player.Name .. " pressed the button!")
wait(1)
debounces[player] = true
end
end)Now each player has their own 1-second cooldown timer. Player A can press the button while Player B is waiting.
Advanced: Debounce with tick()
The wait() approach works, but there's a more precise method using tick(), which returns the current time in seconds:
local lastActivated = 0
local cooldown = 1 -- 1 second cooldown
button.Activated:Connect(function()
local currentTime = tick()
if currentTime - lastActivated >= cooldown then
lastActivated = currentTime
print("Button pressed!")
-- Your code here
end
end)This approach doesn't block execution with wait(). Instead, it checks if enough time has passed since the last successful activation. It's cleaner for high-frequency events (like raycasts or damage checks that run every frame).
tick() debounce in loops or physics callbacks. Use boolean debounce for single events like button clicks.Common Debounce Mistakes
Forgetting to set debounce back to true: Your code runs once and never again because debounce stays false forever. Always pair debounce = false with debounce = true after the wait.
Using global debounce for multiple events: If you have three buttons sharing one debounce flag, pressing any button blocks all of them. Give each event its own debounce variable.
Wait time too short: If your debounce is 0.01 seconds, the player can click twice almost instantly. Test different timings to feel right.
Debounce in the wrong place: The debounce check must be at the start of the event listener, before the actual code. Otherwise, the code runs before it's blocked.
Where to Place Debounce Scripts
- LocalScripts in buttons: Place a LocalScript inside a TextButton or ImageButton in StarterGui to debounce button clicks.
- Server scripts for damage: Put debounce logic in a Script inside the weapon part (like a sword handle) so all players respect the same cooldown.
- LocalScripts for player input: Debounce keyboard or mouse events in a LocalScript in StarterPlayer > StarterCharacterScripts or StarterPlayerScripts.
Quick Recap
- Debounce prevents a function from running multiple times in a short period.
- Use a boolean flag that toggles off and on around your code.
- Always set it back to true after the wait, or it stays blocked forever.
- For damage or continuous checks, consider using
tick()instead of a boolean. - Test different cooldown times until the gameplay feels right.