Tutorials•November 8, 2026•8 min read

Roblox Debounce: A Per-Player Touch Cooldown That Makes Sense

Learn Roblox debounce with a complete per-player Touched cooldown. Choose the right scope, store deadlines correctly, and debug repeated contacts and stuck flags.

A debounce prevents repeated events from running an action too often. In Roblox, a touch can produce several callbacks as different body parts make contact. Decide which action you are limiting, who shares its limit, and when that limit ends before adding a Boolean named debounce.

Separate player timelines accept the first touch, reject an early repeat, and allow a later touch after a two-second deadline
Original timeline for the cooldown rule below. It is a logical example, not a recording of physics events.

What debounce means in Roblox

Roblox’s debounce patterns guide explains the technique as a way to prevent repeated triggers. Its touch example shows why physical contact may invoke a callback several times close together.

In this guide, the first eligible touch runs immediately. Further touches from that player are ignored until two seconds have elapsed. Ignored events are discarded rather than queued. This is a cooldown pattern, sometimes called a leading-edge throttle; it does not wait for a quiet period and then run the last event.

Choose the scope before choosing the variable

Different limits solve different problems
ScopeState to trackExample intention
One shared objectOne flag or deadlineA door cannot begin a second opening animation
Each player on one padDeadline indexed by PlayerOne player’s repeated contacts should not block another
Each player and each actionSeparate action deadlines per PlayerTwo independent abilities have different cooldowns
Work in progressA busy flag until completionA second request must wait until current work finishes

A two-second cooldown does not prove that an earlier operation has finished. A busy lock does not automatically impose a minimum gap after it finishes. Keep those requirements separate when an action yields or calls another service.

Try a complete per-player touch cooldown

Open a separate practice place, insert a regular Script in ServerScriptService, and paste the code. Start a play test with Output visible. The script creates a blue anchored pad at position 0, 1, 0; move your character to it, or change the position to a clear area beside your spawn before testing.

local Players = game:GetService("Players")
local COOLDOWN = 2
local nextAllowed = {}
local acceptedCounts = {}

local pad = Instance.new("Part")
pad.Name = "DebouncePracticePad"
pad.Size = Vector3.new(12, 1, 12)
pad.Position = Vector3.new(0, 1, 0)
pad.Anchored = true
pad.CanCollide = true
pad.CanTouch = true
pad.Color = Color3.fromRGB(13, 78, 241)
pad.Parent = workspace

local function onTouched(hit)
    local character = hit.Parent
    if not character or not character:IsA("Model") then
        return
    end
    local player = Players:GetPlayerFromCharacter(character)
    if not player then return end
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    if not humanoid or humanoid.Health <= 0 then return end

    local now = os.clock()
    local deadline = nextAllowed[player]
    if deadline and now < deadline then
        return
    end
    -- Commit the next deadline before doing the accepted action.
    nextAllowed[player] = now + COOLDOWN
    acceptedCounts[player] = (acceptedCounts[player] or 0) + 1
    print(player.Name, "accepted touch", acceptedCounts[player])
end

pad.Touched:Connect(onTouched)
Players.PlayerRemoving:Connect(function(player)
    nextAllowed[player] = nil
    acceptedCounts[player] = nil
end)

Each accepted touch prints the player’s name and a session-local count. The pad stays blue; the count is the observable result. This example does not give currency, save progress, damage a character, or provide a production reward system. It has been documentation-reviewed but has not been executed here in Studio.

The hit filter accepts body Parts directly parented to a player’s character Model. It deliberately ignores accessory Handles and non-player objects. If your game uses a different character hierarchy, adapt and test that lookup instead of assuming every parent is a character.

Why the deadline is stored before the action

The callback reads the current time, checks the player’s deadline, and writes the next deadline before printing. There is no yielding operation between those steps. A later eligible callback therefore sees the updated deadline.

Luau’s standard library documents os.clock as a high-precision timestamp suitable for measuring durations. This example compares values from the same running server. Do not save this deadline as a calendar time or compare it with an unrelated client’s clock.

An ignored event does not extend the deadline. For example, accepting a touch at time 10 sets the next deadline to 12; contacts at 10.1 and 11.9 leave that deadline at 12. An eligible callback at 12 or later can run. The cooldown expiring by itself does not call onTouched.

Test repeated contacts and two players separately

  1. First contact: walk onto the pad and look for an accepted-touch message.
  2. Rapid re-entry: step off and back on inside two seconds. That player’s count should not increase.
  3. Later re-entry: wait more than two seconds, then make fresh contact. The next eligible callback should increase the count.
  4. Two-player test: use a local server with two players. One player’s cooldown should not suppress the other player’s first eligible touch.
  5. Respawn: verify the new character still maps to the same Player. Counts and deadlines here are keyed to Player and intentionally survive a character respawn.
  6. Leave: the PlayerRemoving callback clears that player’s table entries. Restart the test to reset the whole practice scene.

Roblox’s collision guide discusses repeated Touched events. Do not expect one callback for an entire character or a periodic callback while standing still. A fresh physical contact and an elapsed deadline are separate conditions.

Common debounce bugs and their fixes

The variable resets on every callback

If you create a fresh debounce variable inside onTouched, each callback starts with fresh state. Store the shared state outside the callback. In this example, the tables are created once when the Script starts.

One player blocks everyone else

A single shared flag describes the whole object. Use a table keyed by Player when each player needs an independent limit. For several pads, decide whether each pad should own a separate table or whether an action should share one cooldown across all pads.

The flag never resets after an error

A Boolean busy flag can remain set if later code fails before resetting it. Keep the protected operation small and define cleanup for failure. The timestamp example has no delayed reset task: time makes a deadline eligible again, while PlayerRemoving removes abandoned entries.

The cooldown ends while an action is still running

Increasing the cooldown to a guessed duration does not establish exclusive access. If completing work is the actual condition, use an explicit busy state and release it when work ends, including its failure path. A deadline and a lock can be combined when both conditions matter.

CanCollide is off but contacts still fire

The BasePart reference distinguishes CanCollide from CanTouch. Disabling physical collision is not a debounce strategy. Check touch settings and collision behavior before debugging the timing rule.

Keep gameplay validation separate from event frequency

A cooldown only limits how frequently this callback accepts an action. It does not establish that an interaction is legitimate. Roblox’s client-server boundary guide covers validating client-triggered server behavior. Define eligibility, proximity, ownership, and reward rules before using any interaction for valuable outcomes.

For the Boolean building block, read true and false in Luau. For intentional interactions, see the ProximityPrompt exercise. Use collision groups to control which objects physically collide, and CollectionService when one controller needs to discover several tagged pads. For scheduling an accepted action, compare task.wait, task.delay, task.spawn, and task.defer. Documentation checked November 8, 2026.

Frequently Asked Questions

What does debounce do in Roblox?▾

It limits repeated triggers. The guide uses a cooldown that accepts the first eligible touch and ignores that player’s early repeats without queuing them.

Why does one debounce block other players?▾

A single shared flag applies to the whole object. Use state indexed by Player when each player needs an independent cooldown.

Does standing on a pad trigger it again when the cooldown ends?▾

The deadline expiring does not invoke the callback. The example accepts only a later eligible Touched event; it is not a periodic occupancy timer.

Ready to build your Roblox game?

Create, edit, preview, and export your game for free. No credit card required.

Try Obby — 100% Free →