DEV Community

Cover image for How the Flappy Bird Browser Game Actually Works — Physics, Loops, and a Playable Demo
Rohan Yeole
Rohan Yeole

Posted on

How the Flappy Bird Browser Game Actually Works — Physics, Loops, and a Playable Demo

Flappy Bird is one of the most-cloned games on the internet — and for good reason. It's a near-perfect beginner project: simple physics, one input, instant feedback. But most tutorials stop at "here's the code." Let me walk through how the actual browser mechanics work, and link you to a live version you can play right now.

Play it live → rohanyeole.com/flappy-bird


The Core Physics Loop

Every frame of Flappy Bird runs the same operations in order:

  1. Apply gravity to vertical velocity
  2. Apply vertical velocity to bird's y position
  3. Move all pipes left by pipeSpeed pixels
  4. Check rectangle collision between bird and each pipe
  5. Check floor/ceiling collision
  6. Draw everything

That's it. The entire game is a loop over those six steps.

const gravity = 0.45;
const jumpForce = -8.5;

let birdY = canvas.height / 2;
let birdVel = 0;

document.addEventListener('keydown', (e) => {
  if (e.code === 'Space') birdVel = jumpForce;
});

canvas.addEventListener('click', () => {
  birdVel = jumpForce;
});

function update() {
  birdVel += gravity;
  birdY += birdVel;
}
Enter fullscreen mode Exit fullscreen mode

Gravity tuning is the whole game design. Too high and it's physically impossible; too low and it's trivially easy. The sweet spot is around 0.4–0.5 with a jump force of -8 to -10 for a canvas height of 600px.


Pipe Generation

Pipes come in pairs (top and bottom) with a gap between them. The gap position is randomised, but the gap height stays fixed:

const GAP = 150;
const PIPE_WIDTH = 60;
let pipeSpeed = 3;

function spawnPipe() {
  const topHeight = Math.random() * (canvas.height - GAP - 100) + 50;
  pipes.push({
    x: canvas.width,
    topHeight,
    bottomY: topHeight + GAP
  });
}

// Called every N frames
setInterval(spawnPipe, 1800);
Enter fullscreen mode Exit fullscreen mode

As score increases, you can increase pipeSpeed by 0.1–0.2 every 10 points for a difficulty curve.


Collision Detection

AABB (Axis-Aligned Bounding Box) collision is all you need here:

function collides(bird, pipe) {
  return (
    bird.x < pipe.x + PIPE_WIDTH &&
    bird.x + bird.size > pipe.x &&
    (bird.y < pipe.topHeight || bird.y + bird.size > pipe.bottomY)
  );
}
Enter fullscreen mode Exit fullscreen mode

The bird is treated as a square for collision purposes. The visual is a circle or sprite — the slightly forgiving hitbox is intentional and feels right to players.


Python Flappy Bird (Pygame)

If you want to build this in Python instead, pygame is the standard choice. The physics loop maps 1:1:

GRAVITY = 0.5
JUMP_FORCE = -9

bird_y = 300
bird_vel = 0

while True:
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                bird_vel = JUMP_FORCE

    bird_vel += GRAVITY
    bird_y += bird_vel
    # clamp to screen, check pipe collision...
Enter fullscreen mode Exit fullscreen mode

The logic is identical. The only difference is the rendering layer: canvas.drawImage() in JS vs pygame.draw.* in Python.


Play the Live Version

The version on my site runs entirely in the browser — no login, no install. It's part of a collection of free developer tools I maintain:


What I Learned Building Browser Games as a Backend Dev

I mostly build Django REST APIs and Celery task pipelines — server-side Python. Browser games are an interesting inversion: all state lives in the client, the loop has to run at 60fps, and you can't make a server round-trip mid-frame.

The constraint of requestAnimationFrame teaches you something about deterministic loops that applies back to backend work too. A game loop where state corrupts silently is exactly like a Celery task that swallows exceptions — you need predictable, inspectable state transitions.

If you're a backend developer who hasn't tried building a small canvas game, I'd recommend it as an afternoon project. The fundamentals transfer.


I'm Rohan Yeole — Python/Django developer. If you're building a SaaS backend, REST API, or need Celery + PostgreSQL architecture work, I'm available for hire.


I mostly build Django REST APIs and Celery task pipelines — server-side Python. Browser games are an interesting inversion: all state lives in the client, the loop has to run at 60fps, and you can't make a server round-trip mid-frame.

The constraint of requestAnimationFrame teaches you something about deterministic loops that applies back to backend work too. A game loop where state corrupts silently is exactly like a Celery task that swallows exceptions — you need predictable, inspectable state transitions.

If you're a backend developer who hasn't tried building a small canvas game, I'd recommend it as an afternoon project. The fundamentals transfer.


I'm Rohan Yeole — Python/Django developer. If you're building a SaaS backend, REST API, or need Celery + PostgreSQL architecture work, I'm available for hire.


Top comments (1)

Collapse
 
hiroshi_takamura_c851fe71 profile image
Hiroshi TK

The bit about the hitbox being intentionally smaller than the visual is such a good call out, that's a classic game feel trick and most clones skip it then wonder why their version feels unfair.
I actually built a Flappy Bird sim on itembase.dev/sim a while back to test the gravity and jump force numbers, so seeing the full JS implementation laid out like this was a fun full circle moment. The tuning really is the whole game, messing with those two values changes the feel completely.