Quick answer: A Timer only counts down while it is inside the scene tree, has been started (or has Autostart on), and is allowed to process. The usual culprits are calling start() every frame, a paused SceneTree, Engine.time_scale set to 0, or a timeout connection that never happened.
You add a Timer, connect timeout, run the scene and nothing happens. Timers have very few moving parts, so when one stays silent it is almost always one of six things. Work through them in order.
1. The timer is never started
A Timer does nothing until start() is called or Autostart is enabled in the Inspector. Autostart only applies when the node enters the tree, so a Timer you create in code needs an explicit start() after add_child():
var t := Timer.new()
t.wait_time = 2.0
t.one_shot = true
add_child(t) # must be in the tree first
t.timeout.connect(_on_timeout)
t.start()
Calling start() on a Timer that is not in the tree prints "Timer was not added to the SceneTree" and does nothing, so check the Output panel.
2. You restart it every frame
start() resets the countdown. If it runs from _process() or _physics_process(), or from an input handler that fires repeatedly, the timer never reaches zero:
func _process(delta):
if enemy_visible:
$AttackTimer.start() # resets every frame -- never fires
Guard it so you only start a timer that is not already running:
if enemy_visible and $AttackTimer.is_stopped():
$AttackTimer.start()
3. The signal is not connected
If you connected in the editor, the Node dock shows a green icon next to the script method. In code, Godot 4 uses callables: timer.timeout.connect(_on_timeout). The Godot 3 form connect("timeout", self, "_on_timeout") fails in Godot 4. Also make sure the handler takes no arguments, because timeout passes none.
4. The game is paused
When get_tree().paused is true, nodes whose Process > Mode is Inherit or Pausable stop processing, and Timers stop counting. For a timer that must keep running in a pause menu, set its process mode to Always:
$MenuTimer.process_mode = Node.PROCESS_MODE_ALWAYS
5. Engine.time_scale is 0
Timers count scaled time. A slow-motion or hit-stop effect that sets Engine.time_scale = 0.0 freezes every Timer in the game until it is restored. For UI that must ignore it, use get_tree().create_timer(duration, true, false, true), whose last argument ignores the time scale.
6. One-shot behaviour or a stop() elsewhere
With One Shot on, the timer fires once and stops; with it off, it repeats every wait_time. If it fires once when you expected a loop, or loops when you expected one shot, check that flag. Then search the project for .stop() calls on that timer — a state change that stops it before it finishes looks exactly like a timer that never fires.
A quick diagnostic
Print the timer's state from _process for a second: if time_left is not decreasing, it is paused, stopped or being restarted; if it reaches zero and nothing prints, the connection is the problem.
func _process(_d):
print($MyTimer.is_stopped(), " ", $MyTimer.time_left)
For quick delays that do not need a node, await get_tree().create_timer(1.0).timeout avoids most of these setup mistakes.