How to Fix a Game Error in a Nested Array with JavaScript

Uncaught TypeError Cannot read property ‘x’ of undefined, if you’ve seen this error while working with nested arrays in JavaScript, you’re not alone. I ran into this same issue while trying to create a simple game with bouncing circles using the <canvas> element. It was frustrating at first, but once I understood the problem, the fix was actually quite straightforward.

The Problem

In my original code, I tried to draw several animated circles on a canvas. Each circle was stored inside a nested array called circles. But when I ran the project, I got this error:

Uncaught TypeError: Cannot read property 'x' of undefined

And it pointed to this line:

ctx.arc(circles[i].x, circles[i].y, circles[i].r, 0, Math.PI * 2, false);

This really puzzled me. I had defined the array properly. So what was going on?

Why This Happen

After some debugging, I realized the problem was with how I was using the for loop and variable i.

for (var i = 0; i < circles.length; i++) {
// All logic and drawing functions declared here
setTimeout(draw, 10); // <- here's the trap
}

The issue is that I wrapped everything including function definitions and setTimeout(draw, 10) inside a for loop and assumed i would still be valid when the draw() function was called.

But by the time setTimeout(draw, 10) ran, the loop had already completed, and i was out of bounds (equal to circles.length). So when the draw function tried to access circles[i], it was undefined hence the error.

Loop Dynamically Inside the Draw Function

What I needed to do was restructure the code:

  • Move all animation and drawing inside the draw() function.
  • Let draw() loop through the circles array every frame.
  • Handle each circle’s movement and drawing independently.

The Final, Working Code

<!DOCTYPE html>
<html>
<head>
<title>Canvas Game: Bouncing Circles</title>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<script type="text/javascript">
// Define your circles
const circles = [
{x: 200, y: 150, r: 40, direction: 1, speedX: 1, speedY: 2, color: "yellow"},
{x: 100, y: 100, r: 30, direction: 2, speedX: 2, speedY: 1, color: "red"},
{x: 300, y: 200, r: 20, direction: 3, speedX: 1.5, speedY: 1.5, color: "blue"}
];

function moveCircle(circle) {
// Move based on direction
switch (circle.direction) {
case 1: circle.x += circle.speedX; circle.y += circle.speedY; break;
case 2: circle.x += circle.speedX; circle.y -= circle.speedY; break;
case 3: circle.x -= circle.speedX; circle.y -= circle.speedY; break;
case 4: circle.x -= circle.speedX; circle.y += circle.speedY; break;
}

// Bounce off edges
if (circle.y > 300 - circle.r && circle.direction === 1) {
circle.direction = 2;
} else if (circle.x > 400 - circle.r && circle.direction === 2) {
circle.direction = 3;
} else if (circle.y <= circle.r && circle.direction === 3) {
circle.direction = 4;
} else if (circle.x <= circle.r && circle.direction === 4) {
circle.direction = 1;
} else if (circle.y <= circle.r && circle.direction === 2) {
circle.direction = 1;
} else if (circle.x >= 400 - circle.r && circle.direction === 1) {
circle.direction = 4;
} else if (circle.y >= 300 - circle.r && circle.direction === 4) {
circle.direction = 3;
} else if (circle.x <= circle.r && circle.direction === 3) {
circle.direction = 2;
}
}

function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

// Clear canvas
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);

// Move and draw each circle
for (let i = 0; i < circles.length; i++) {
const circle = circles[i];
moveCircle(circle);
ctx.fillStyle = circle.color;
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.r, 0, Math.PI * 2);
ctx.fill();
}

requestAnimationFrame(draw); // Smooth animation
}

window.onload = draw;
</script>
</head>
<body>
<canvas id="canvas" width="400" height="300"></canvas>
</body>
</html>

Improvement I Made

To go beyond just fixing the bug, I decided to improve the whole project:

  • Used requestAnimationFrame() instead of setTimeout() for smoother animations and better performance.
  • Allowed multiple circles to animate at once, each with its own speed, direction, and color.
  • Organized the movement logic into its own function moveCircle() to keep the code clean and readable.
  • Added random colors to easily tell circles apart makes debugging easier too!

Ideas for More Practice

Once I got this working, I started thinking of ways to build on it:

  • Click to add a new circle — let users interact with the canvas.
  • Randomize properties like color, radius, and speed for each circle.
  • Add collisions between circles — a great way to practice physics and hit detection.
  • Pause/resume the game using keyboard input.
  • Level up by adding targets, scoring, and lives — turning it into an actual game.

Let me know if you want me to help with any of these ideas I’d be happy to break those down in future posts!

Final Thought

This experience was a great reminder that understanding variable scope and timing in JavaScript is essential, especially when dealing with asynchronous operations like setTimeout() or animations. What seemed like a bug in my data (circles[i]) was actually a problem with how and when that data was accessed.

Related blog posts