Building a Snake Game From Scratch
I spent three hours last Tuesday debugging why my snake kept reversing into itself instead of growing. Turns out the direction queue handling was the culprit. The basic concept is simple. You have a grid, a snake made of segments, and apples that spawn randomly. The snake moves one cell per frame. Press left or right to turn. Eat an apple to grow longer. Hit a wall or yourself and the game ends. That's it. Here's the minimal setup I use when starting a new project:
Snake Game With Apples Core Structure
Create a canvas element. Set it to 400x400 pixels. Use a 20x20 grid, so each cell is 20 pixels. Initialize the snake as an array of coordinates starting at the center: [{x:10, y:10}, {x:9, y:10}, {x:8, y:10}]. Set the initial direction to 'right'. The game loop runs every 100 milliseconds. Each tick, calculate the new head position based on the current direction, check collisions, then update the snake array. If the head lands on an apple coordinate, add a segment instead of removing the tail. Apple spawning needs a collision check. Don't just place it randomly anywhere. Check against the snake body. Otherwise you get apples appearing inside the snake, which looks broken.
I hit a weird edge case where the snake would reverse direction if you pressed two keys fast enough in one frame. My workaround: buffer input. Track the last applied direction separately from the current input. Only process one direction change per tick. This solved the instant-reverse bug completely.
Get the Full Details

Implementation Details
Use requestAnimationFrame for smooth rendering, but throttle the game logic with setInterval or a manual timestamp check. The render frequency and game speed are separate concerns. You might render at 60fps while the snake only moves every 100ms. Collision detection is straightforward. Check if head.x is less than 0 or greater than gridWidth. Same for y. For self-collision, loop through the body segments starting from index 1 (not 0, since the neck shouldn't collide with the head). If any segment equals the head position, game over. One counter-intuitive thing: the apple should spawn outside the snake bounds AND inside the canvas. A common beginner mistake is forgetting the bounds check. I've seen apples spawn at negative coordinates, invisible but still counted as eaten when the snake reaches them, causing undefined behavior.
Here's the basic loop structure: Get current time. Calculate delta. If delta exceeds the move interval, process game logic. Clear canvas. Draw background. Draw apple at its position. Draw each snake segment from tail to head. Update state. Repeat. Controls: arrow keys and WASD both work fine. Map both sets to the same directions. Prevent default on arrow key presses so the page doesn't scroll while playing.
Common Pitfalls
Snake growing logic often has off-by-one errors. When you eat an apple, append a new segment. But the new segment's position depends on whether you add it before or after removing the tail. If you add before removing, the snake grows by one. If you remove first, then add, it stays the same length. Make sure your order is consistent. Another issue: multiple apples spawning simultaneously. Track apples as an array instead of a single coordinate. If you want multiple apples on screen, manage the list properly. Remove eaten apples from the array and spawn new ones only if the count falls below your target. The game feels sluggish if you use setTimeout instead of requestAnimationFrame. Render timing becomes tied to script execution. Use rAF for drawing and a separate timer for game ticks.

One more thing people miss: the snake head should always draw on top. Iterate the body array backwards when rendering, so the head appears last and renders over the neck. Otherwise you get visual artifacts where the head seems partially transparent or displaced. I tested this on a 600x600 grid with a 50ms tick rate. The snake became nearly impossible to control past length 30. Recommendation: keep tick rate between 80-120ms for casual play. Anything faster demands precise timing most players can't maintain.