Building a Basic 2 Player Pong Game
Pong is one of those projects that sounds trivial until you actually sit down to code it. Everyone thinks they can knock it out in an afternoon. Some people can. Most don't realize why until they're three hours in and the ball is phase-shifting through paddles on a regular basis. I'm going to walk through building a functional 2 Player Pong game from scratch, the kind you'd run in a browser. We'll use HTML5 Canvas and vanilla JavaScript. No frameworks, no libraries. You pick your own path from there if you want to port it somewhere else later.
Core Game Loop and Frame Management
The foundation is a proper game loop. Not just setInterval slopping frames together at whatever speed the browser feels like handing them out. You need requestAnimationFrame with delta time calculations so movement stays consistent regardless of refresh rate. I learned this the hard way on my first attempt. The ball moved at wildly different speeds on a 60Hz monitor versus a 144Hz screen, which broke every timing assumption I'd built into the collision system. Your loop should grab the current timestamp, subtract the previous frame's timestamp, and apply that delta to every moving object. This single change made the game feel noticeably more polished across different hardware setups without any extra work.
2 Player Pong: Positional State and Input Handling
You need four primary pieces of state tracking: two paddle positions and two ball positions, plus the ball's velocity vector. Each paddle occupies a vertical range on its respective side of the screen. Player 1's paddle moves with W and S keys. Player 2 uses the arrow up and arrow down keys. Track these as boolean flags in your input handler rather than moving paddles directly on keydown events. That approach prevents the operating system's key repeat delay from creating stuttery movement and lets you process all input per frame consistently. Here's the practical setup for input: Listen for keydown and keyup events. Set flags to true or false. In your update loop, check the flags and apply paddle velocity. This decouples input from physics and keeps everything deterministic.
Get the Full Details

I ran into a specific issue early on that I wish I'd figured out sooner. When both players press their upward keys simultaneously while the ball is near the center of the screen, the collision detection would occasionally register the ball as passing through a paddle entirely within a single frame. This happened because the ball's velocity was high enough that its position jumped from clearly above the paddle to clearly below it between frames. The fix was implementing continuous collision detection by checking not just whether the ball overlaps the paddle at its new position, but whether its trajectory line segment intersects the paddle's rectangular bounds during the frame. A simple swept-rectangle test solved this completely.
Collision Detection and Ball Physics
Wall collisions are straightforward: reverse the Y velocity when the ball hits the top or bottom boundary. Paddle collisions require a bit more thought. When the ball hits a paddle, you shouldn't just reverse the X velocity and call it a day. That creates repetitive, predictable rallies that get boring fast. The angle the ball reflects at should depend on where it strikes the paddle. A hit near the center produces a near-horizontal bounce. A hit near the top or bottom edge of the paddle sends the ball at a sharper angle. Calculate this by normalizing the hit position along the paddle's height to a range between negative one and positive one, then mapping that to your desired maximum deflection angle. Multiply that value by the current X speed to create the new Y velocity component, and reverse the X velocity. There's a common pitfall here that catches people off guard. If you clamp the ball's speed too aggressively after each paddle hit, the game becomes sluggish and unenjoyable. If you don't clamp it at all, the ball eventually reaches speeds where collision detection breaks again and the delta-time issue resurfaces. I settled on a moderate speed multiplier that increases slightly with each rally exchange, capped at a reasonable maximum. This keeps matches tense without becoming technically unplayable.
Scoring and Game State Reset
When the ball passes a paddle, that's a point for the opposing player. Reset the ball to the center of the screen with a randomized initial direction and standard starting speed. Don't reset to the same direction every time, or the receiving player gains an unfair read on the opening rally. A 45-degree randomization in either direction works fine. Decide on a win condition before you start coding this part. First to seven points is standard. Track scores as simple integer variables and check after each point whether either player has reached the threshold. Display scores prominently at the top center of the canvas using basic fillText calls. Nothing fancy needed here.

Putting It Together: The Full Structure
Your main JavaScript file should be organized into these sections: constant definitions, game state object, input handlers, update logic, rendering logic, and the animation loop. Keeping them separate makes debugging significantly easier. When something breaks, you can usually pinpoint which section is responsible within a minute or two instead of hunting through a monolithic script. The HTML file is minimal. A single canvas element with inline dimensions or CSS sizing, and a script tag referencing your JavaScript file. That's it. The browser handles the rest. One thing worth noting about this approach: it works well for learning and casual play, but it has limitations. The collision detection, even with the swept-rectangle fix, can still fail under extreme conditions like very high ball speeds combined with very thin paddles. If you plan to extend this into something more robust, consider switching to a proper physics library or implementing sub-stepping, where you run the physics simulation multiple times per frame with smaller delta values. Sub-stepping adds computational cost but dramatically improves reliability at high speeds.
For a simple browser-based project though, the approach described here is more than adequate. You'll have a working 2 Player Pong in roughly two to three hours if you're writing the code deliberately rather than copy-pasting. The learning value comes from actually implementing each piece yourself, especially the collision math and the delta-time loop. Those are concepts that show up in nearly every game development scenario you'll encounter later.