# Delta Time

> Delta time is the seconds since the last frame. Multiply speeds and rates by it so a game plays the same at 30, 60 or 144 fps.

- Category: Game Feel › Timing
- Level: core
- Tags: delta time, frame rate, game loop, frame-rate independence, requestAnimationFrame
- Updated: 2026-11-01
- URL: https://gdcs.coody.app/game-feel/timing/delta-time

## What it is

Frames do not arrive at a steady rate. A 144 Hz monitor runs the loop more than twice as often as a 60 Hz one, and a busy frame can take 40 ms instead of 16. If you move a player by `5` units **per frame**, their speed depends on the hardware. Delta time `dt` is the real time the previous frame took. Write speeds in units **per second** and multiply by `dt`, and motion becomes frame-rate independent.

In the browser, `requestAnimationFrame` hands your callback a timestamp in milliseconds. `dt` is that timestamp minus the previous one, divided by 1000. In React Three Fiber the second argument of `useFrame` is already `dt` in seconds.

Linear changes (`x += v·dt`) scale exactly. Anything that compounds per frame does not: `v *= 0.9` each frame is friction that is stronger at high fps. Use `Math.pow(0.9, dt·60)` or the exponential form `1 − e^(−k·dt)` instead. For physics or anything that must be deterministic, accumulate `dt` and step a [fixed timestep](https://gdcs.coody.app/systems/simulation/fixed-timestep).

## Key formulas

- **Position update:** $x_{t+\Delta t} = x_t + v\,\Delta t$
- **Frame-rate independent decay:** $v \leftarrow v \cdot f^{\,\Delta t \cdot 60} = v\,e^{-k\,\Delta t}$ — f is the per-frame factor you tuned at 60 fps; k = −60·ln f is the same decay per second.
- **Exponential smoothing toward a target:** $x \leftarrow x + (x_{target} - x)\,(1 - e^{-k\,\Delta t})$

## Rules of thumb

- Clamp `dt` to about 0.1 s (or 0.25 s). A tab that was in the background can report several seconds and teleport everything.
- Store speeds in units/s, accelerations in units/s², and rates as per-second constants. Never tune in "per frame" units.
- Pause by setting `dt = 0`; slow motion by `dt × timeScale`. Keep a separate unscaled `dt` for UI and menus.
- Gravity with semi-implicit Euler (`v += g·dt; x += v·dt`) is fine for gameplay. Use a fixed step if jump height must be identical everywhere.

## Implementation

A minimal browser loop with clamped delta time:

```ts
let last = performance.now()
const speed = 4 // units per second

function frame(now: number) {
  const dt = Math.min((now - last) / 1000, 0.1) // seconds, clamped
  last = now

  player.position.x += speed * dt
  // per-frame friction 0.9 tuned at 60 fps, made fps-independent
  velocity *= Math.pow(0.9, dt * 60)

  renderer.render(scene, camera)
  requestAnimationFrame(frame)
}
requestAnimationFrame(frame)
```

## Common mistakes

- Moving by a constant amount per frame. The game runs 2.4× faster on a 144 Hz display than on 60 Hz.
- Multiplying a per-frame damping factor without converting it: `v *= 0.98` is much stronger at high frame rates.
- Not clamping `dt` after a pause, breakpoint or hidden tab, so objects tunnel through walls on the next frame.
- Applying `dt` twice, e.g. to the velocity and again to an acceleration that was already integrated.
- Expecting variable `dt` to give deterministic physics. Replays and networking need a fixed step.

## Interactive visual

Two runners race under a simulated frame rate. Cyan multiplies its speed by delta time; red moves a fixed amount per frame tuned for 60 fps. The tick marks are individual frames. Drop the FPS slider and red falls behind; raise it above 60 and red wins. Turn on uneven frames and cyan still arrives on time.

## Prompt

```text
Write a TypeScript game loop for a Three.js scene using requestAnimationFrame. Compute dt in seconds from the rAF timestamp, clamp it to 0.1 s, and support a timeScale (0 = paused) while also passing an unscaled dt to UI code. Move a player at a speed in units/s, apply friction as v *= Math.pow(f, dt * 60), and smooth a camera with x += (target - x) * (1 - Math.exp(-k * dt)). Put the pure update in a function and write a Vitest test that runs it at 30 and 144 fps for 2 s and asserts positions match within 1%.
```

## Related

- [Fixed Timestep](https://gdcs.coody.app/systems/simulation/fixed-timestep.md) — Run physics in constant dt slices from an accumulator, clamp long frames, and interpolate between the last two states for smooth, repeatable motion.
- [Frame Budget](https://gdcs.coody.app/performance/cpu/frame-budget.md) — At 60 Hz a frame has 16.67 ms for input, gameplay, physics, AI and rendering combined. Measure each system and keep the total under budget with headroom.
- [Acceleration and Friction](https://gdcs.coody.app/game-feel/motion/acceleration-and-friction.md) — Ramp velocity toward input × max speed with an acceleration, and back to zero with a separate deceleration, to give movement weight.
- [Hit Stop](https://gdcs.coody.app/game-feel/timing/hit-stop.md) — Hit stop pauses the attacker and target for a few frames on impact. The brief freeze sells weight and gives the eye time to read the hit.

## Further reading

- [Gaffer On Games — Fix Your Timestep!](https://gafferongames.com/post/fix_your_timestep/)
- [MDN — Window.requestAnimationFrame()](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame)
- [Game Programming Patterns — Game Loop](https://gameprogrammingpatterns.com/game-loop.html)

---
Source: Game Development Cheat Sheet (https://gdcs.coody.app)
