Summarize this article with:

Every game you play in your browser runs on one fundamental mechanism: the JavaScript game loop.

Without it, nothing moves. No animations, no physics, no gameplay.

This continuous execution cycle updates game state and renders frames dozens of times per second, creating the illusion of motion and interactivity.

Whether you’re building a simple Canvas animation or a complex WebGL experience, understanding how the loop works separates broken projects from smooth, professional games.

You’ll learn how requestAnimationFrame controls timing, why delta time matters for frame rate independence, and which implementation patterns prevent performance bottlenecks.

What is JavaScript Game Loop

A JavaScript game loop is a continuous execution mechanism that repeatedly updates game state and renders visual output at consistent intervals.

It forms the core timing architecture for HTML game development, controlling how game objects move, physics simulations run, and graphics appear on screen.

Without this repeating cycle, games would freeze after a single frame.

Core Components

The Frame Update Mechanism

The loop executes code repeatedly, typically 60 times per second, creating the illusion of motion.

Each iteration processes input, updates positions, calculates collisions, and redraws the canvas rendering context.

Modern browsers synchronize these updates with display refresh rates through requestAnimationFrame.

Time Management

Delta time tracks milliseconds elapsed between frames, preventing speed variations across different hardware.

What is shaping UX design today?

Uncover the newest UX design statistics: user behavior, design trends, ROI data, and insights driving better digital experiences.

Check Them Out →

Games running on slow devices maintain correct physics by adjusting movement distances based on frame duration rather than frame count.

A timestamp parameter passed to each callback function provides precise timing data.

State Updates

Game state holds all entity positions, velocities, health values, and active status flags.

The update function modifies this data structure every frame based on physics rules, user input, and API responses.

State changes happen before rendering to ensure visual consistency.

Rendering Cycle

The draw function clears previous graphics and paints updated visuals using HTML Canvas or WebGL contexts.

Separating rendering from logic updates allows advanced techniques like interpolation between physics steps.

Some engines render at different rates than physics calculations for performance optimization.

Implementation Methods

requestAnimationFrame Approach

YouTube player

This browser API synchronizes callbacks with the display’s vertical refresh, typically 60Hz.

function gameLoop(timestamp) {
  updateGameState(timestamp);
  renderFrame();
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

Built-in throttling prevents excessive CPU usage when tabs lose focus.

setInterval Pattern

Older implementations used fixed millisecond intervals, independent of screen refresh.

setInterval(() => {
  update();
  render();
}, 16.67);

This creates timing drift and doesn’t pause during browser tab switches, wasting resources.

setTimeout Recursive Method

Each frame schedules the next using setTimeout, allowing dynamic interval adjustments.

function loop() {
  update();
  render();
  setTimeout(loop, targetFrameTime);
}

More flexible than setInterval but still inferior to requestAnimationFrame for visual synchronization.

Delta Time Calculation

Why Delta Time Matters

Frame rates fluctuate based on device performance, background processes, and scene complexity.

Moving objects by fixed pixels per frame makes games run faster on high-end hardware and slower on budget devices.

Frame-independent movement uses elapsed time to calculate distance, ensuring consistent speed across all systems.

Calculating Delta Time

YouTube player

Subtract the previous frame’s timestamp from the current one to get milliseconds elapsed.

let lastTime = 0;

function loop(currentTime) {
  const deltaTime = currentTime - lastTime;
  lastTime = currentTime;
  
  updatePositions(deltaTime);
  requestAnimationFrame(loop);
}

Convert to seconds by dividing by 1000 for more intuitive velocity calculations.

Applying Delta Time to Movement

Multiply velocity vectors by delta time to get frame-adjusted displacement.

player.x += player.velocityX * (deltaTime / 1000);
player.y += player.velocityY * (deltaTime / 1000);

A character moving at 100 pixels per second travels exactly 100 pixels in one second regardless of frame rate.

Fixed vs Variable Time Steps

Variable Time Step

Updates happen whenever a frame renders, using whatever delta time occurred since the last frame.

Simple to implement and automatically adapts to hardware capabilities.

Physics simulations can become unstable during lag spikes when large time deltas cause objects to tunnel through barriers.

Fixed Time Step

Physics updates run at constant intervals (often 50 or 60 times per second) independent of rendering speed.

const FIXED_TIMESTEP = 1/60;
let accumulator = 0;

function loop(currentTime) {
  const deltaTime = (currentTime - lastTime) / 1000;
  lastTime = currentTime;
  accumulator += deltaTime;
  
  while (accumulator >= FIXED_TIMESTEP) {
    updatePhysics(FIXED_TIMESTEP);
    accumulator -= FIXED_TIMESTEP;
  }
  
  render();
  requestAnimationFrame(loop);
}

Predictable, deterministic behavior makes networked multiplayer synchronization possible.

Hybrid Approaches

Some engines use fixed physics steps with variable rendering, interpolating visual positions between physics states.

The accumulator pattern runs multiple physics updates per frame when rendering falls behind.

Performance Optimization

Frame Rate Monitoring

Track FPS by counting frames rendered per second and display the metric using an on-screen counter.

Performance profiling tools reveal bottlenecks in update or render functions that cause frame drops below 60.

let frameCount = 0;
let lastFpsUpdate = 0;

function loop(currentTime) {
  frameCount++;
  if (currentTime - lastFpsUpdate >= 1000) {
    console.log(`FPS: ${frameCount}`);
    frameCount = 0;
    lastFpsUpdate = currentTime;
  }
}

Loop Efficiency

Minimize object creation inside the loop to reduce garbage collection pauses.

Reuse arrays and objects rather than instantiating new ones every frame.

Cache calculations that don’t change between iterations, like distance thresholds or collision boundaries.

Memory Management

Preallocate object pools for frequently created entities like bullets or particle effects.

Clear references to destroyed game objects immediately so the garbage collector can reclaim memory.

Profile memory usage in browser DevTools to identify leaks where objects persist after removal from active state.

Common Patterns

Basic Game Loop Structure

let lastTime = 0;

function gameLoop(currentTime) {
  const deltaTime = (currentTime - lastTime) / 1000;
  lastTime = currentTime;
  
  handleInput();
  updateGameLogic(deltaTime);
  renderScene();
  
  requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

Three distinct phases per iteration: input processing, state updates, visual rendering.

Physics-Based Loop

Separate physics calculations from rendering to maintain stable simulations.

const PHYSICS_STEP = 1/60;
let accumulator = 0;

function loop(currentTime) {
  accumulator += deltaTime;
  
  while (accumulator >= PHYSICS_STEP) {
    updatePhysics(PHYSICS_STEP);
    accumulator -= PHYSICS_STEP;
  }
  
  render(accumulator / PHYSICS_STEP);
}

The interpolation factor passed to render smooths visuals between physics states.

Multi-Layer Update System

Different game systems run at different frequencies based on priority.

Physics updates every frame, AI decisions every 100ms, pathfinding every 500ms.

let aiTimer = 0;

function update(deltaTime) {
  updatePhysics(deltaTime);
  
  aiTimer += deltaTime;
  if (aiTimer >= 0.1) {
    updateAI();
    aiTimer = 0;
  }
}

Handling Input

Input Processing Timing

See the Pen
Game Input Demo
by Bogdan Sandu (@bogdansandu)
on CodePen.

Poll input state at the beginning of each frame before physics updates.

Store key states in a lookup object that update functions query during execution.

const keys = {};

window.addEventListener('keydown', e => keys[e.code] = true);
window.addEventListener('keyup', e => keys[e.code] = false);

function handleInput() {
  if (keys['ArrowLeft']) player.velocityX = -200;
  if (keys['ArrowRight']) player.velocityX = 200;
}

Event Buffering

Queue input events that occur between frames and process them in batch.

Critical for fighting games where frame-perfect timing matters.

Prevent input loss during heavy processing by capturing events immediately and executing them during the update phase.

Frame Rate Independence

YouTube player

Consistent Physics Behavior

Multiply all velocity values by delta time to maintain movement speed regardless of FPS.

A 100-pixel-per-second velocity moves the same distance whether running at 30 FPS or 144 FPS.

Without delta time, games become unplayable across different hardware specifications.

Animation Timing

Advance sprite animation frames based on elapsed time rather than loop iterations.

sprite.frameTime += deltaTime;
if (sprite.frameTime >= sprite.frameDuration) {
  sprite.currentFrame = (sprite.currentFrame + 1) % sprite.totalFrames;
  sprite.frameTime = 0;
}

Characters animate at consistent speeds even when rendering slows down.

Browser Compatibility

Cross-Browser Support

Modern browsers all support requestAnimationFrame, but older implementations required vendor prefixes.

const raf = window.requestAnimationFrame || 
            window.webkitRequestAnimationFrame || 
            window.mozRequestAnimationFrame;

Test on Chrome, Firefox, Safari, and Edge to catch platform-specific timing quirks.

Polyfills and Fallbacks

Implement setTimeout-based fallback for ancient browsers lacking requestAnimationFrame.

if (!window.requestAnimationFrame) {
  window.requestAnimationFrame = callback => {
    return setTimeout(() => callback(Date.now()), 16);
  };
}

Most progressive web apps targeting modern devices skip polyfills entirely.

Debugging Game Loops

Common Issues

Frame stuttering occurs when update logic takes longer than 16.67ms to execute.

Objects moving too fast indicate missing delta time multiplication.

Physics glitches like tunneling happen when collision detection runs after large time steps.

Diagnostic Tools

Browser performance profilers show exactly which functions consume the most CPU time per frame.

Add console logging for delta time values to detect irregular frame pacing.

function loop(currentTime) {
  const delta = currentTime - lastTime;
  if (delta > 50) console.warn(`Slow frame: ${delta}ms`);
  // rest of loop
}

Debug Overlays

Display real-time metrics directly on the canvas using text rendering.

Show FPS, delta time, object count, and memory usage in a corner overlay.

function renderDebugInfo(ctx) {
  ctx.fillStyle = 'white';
  ctx.font = '14px monospace';
  ctx.fillText(`FPS: ${currentFPS}`, 10, 20);
  ctx.fillText(`Objects: ${gameObjects.length}`, 10, 40);
}

Advanced Techniques

Interpolation and Extrapolation

Render visual positions between physics states for ultra-smooth motion at any frame rate.

const alpha = accumulator / FIXED_TIMESTEP;
const renderX = previousX + (currentX - previousX) * alpha;

Players perceive smoother animation even when physics runs at lower frequency than rendering.

Multithreading with Web Workers

Offload pathfinding, procedural generation, or complex calculations to background threads.

Main game loop continues running at 60 FPS while workers process heavy tasks asynchronously.

Data transfer between threads requires serialization, adding overhead for frequent communication.

Pause and Resume Logic

Stop the loop by not calling requestAnimationFrame recursively.

let isPaused = false;

function loop(currentTime) {
  if (!isPaused) {
    update(deltaTime);
    render();
    requestAnimationFrame(loop);
  }
}

function pauseGame() {
  isPaused = true;
}

function resumeGame() {
  isPaused = false;
  lastTime = performance.now();
  requestAnimationFrame(loop);
}

Reset timing variables when resuming to prevent massive delta time jumps.

FAQ on JavaScript Game Loop

What is the difference between setInterval and requestAnimationFrame?

setInterval runs at fixed millisecond intervals regardless of browser activity or screen refresh rates.

requestAnimationFrame synchronizes with the display’s vertical refresh, pauses when tabs lose focus, and provides timestamp parameters for precise delta time calculations. Always prefer requestAnimationFrame for game loops.

How do I calculate delta time in a game loop?

Subtract the previous frame’s timestamp from the current timestamp to get milliseconds elapsed.

const deltaTime = currentTime - lastTime;
lastTime = currentTime;

Divide by 1000 to convert to seconds for velocity calculations.

Why does my game run faster on some computers?

You’re not using delta time to make movement frame rate independent.

Moving objects by fixed pixels per frame means high-end machines process more frames and move objects farther. Multiply all velocities by delta time to normalize speed across hardware.

What is a fixed time step?

Physics updates run at constant intervals (typically 60 times per second) independent of rendering speed.

An accumulator pattern stores leftover time and executes multiple physics steps per frame when rendering falls behind. Provides deterministic, stable simulations critical for multiplayer games.

How do I pause and resume a game loop?

Stop calling requestAnimationFrame recursively when pausing.

Set a boolean flag, check it each frame, and only continue the loop when unpaused. Reset your lastTime variable when resuming to prevent massive delta time jumps that cause objects to teleport.

What causes frame stuttering in game loops?

Update or render functions taking longer than 16.67ms to execute.

Excessive object creation triggers garbage collection pauses. Profile your code with browser DevTools to identify bottlenecks. Cache calculations, reuse objects through pooling, and separate heavy computations into Web Workers.

Should physics and rendering run at the same rate?

Not necessarily. Many engines use fixed time step physics at 60Hz with variable rendering.

This decouples simulation stability from visual frame rate. Interpolate visual positions between physics states for smooth motion even when frame rates fluctuate or physics runs slower than rendering.

How do I handle input in a game loop?

Poll keyboard and mouse state at the start of each frame before physics updates.

Store input states in an object that update functions query during execution. Buffer events that occur between frames and process them in batch to prevent input loss during heavy processing.

What is interpolation in game loops?

Rendering visual positions between physics states for smoother motion.

When physics runs at fixed intervals but rendering varies, calculate an alpha factor (accumulator / fixedTimeStep) and blend previous and current positions. Players perceive ultra-smooth animation regardless of update frequency differences.

How do I optimize game loop performance?

Minimize object allocation inside the loop, cache repeated calculations, and use object pools.

Profile with browser DevTools to find bottlenecks. Separate rendering and physics frequencies. Offload pathfinding or procedural generation to Web Workers. Reduce draw calls by batching sprite rendering operations.

Conclusion

Mastering the JavaScript game loop transforms static code into fluid, interactive experiences.

You now understand how requestAnimationFrame synchronizes with browser refresh rates, why delta time prevents speed variations across hardware, and when to implement fixed versus variable time steps.

The patterns covered here apply whether you’re building HTML5 arcade games, physics simulations with Three.js, or complex multiplayer experiences using Phaser.

Performance optimization through object pooling, efficient state management, and proper frame rate independence separates amateur projects from professional game development.

Start with the basic loop structure, add delta time calculations, then layer in advanced techniques like interpolation as your projects demand them.

The continuous execution cycle you’ve learned powers every browser game running right now.

Author

Bogdan Sandu specializes in web and graphic design, focusing on creating user-friendly websites, innovative UI kits, and unique fonts.Many of his resources are available on various design marketplaces. Over the years, he's worked with a range of clients and contributed to design publications like Designmodo, WebDesignerDepot, and Speckyboy among others.