STR Coding Club logo STR Coding ClubLearn to code, together
Games

Conway's Game of Life Exposes the Classic 2D List Bug

The most common message I get from students working on Conway's Game of Life is a screenshot of a grid where one click lit up an entire vertical stripe. They clicked one cell. Twenty cells turned on.

A grid of glowing cells forming a glider pattern

The most common message I get from students working on Conway's Game of Life is a screenshot of a grid where one click lit up an entire vertical stripe. They clicked one cell. Twenty cells turned on. They have checked the click handler four times and it is fine. They are convinced Python is broken.

Python is not broken. The grid was built with one line that every beginner writes and almost nobody is warned about, and the Game of Life happens to be the perfect place for that bug to show itself. In contest coaching I see the same mistake cost people points years later, in problems that have nothing to do with cellular automata. Better to meet it here, in a toy, with a stripe on the screen that makes the cause visible.

A zero player game with very strict rules

John Conway's Game of Life became famous after Martin Gardner described it in Scientific American in October 1970. It is called a zero player game because you only set up the starting pattern and then watch. The grid follows four rules each generation. A live cell with fewer than two live neighbours dies. One with two or three survives. One with more than three dies. A dead cell with exactly three live neighbours comes alive.

Those rules are why the game exposes bugs so cleanly. Known patterns behave in exact, checkable ways. A blinker, three cells in a row, flips between horizontal and vertical every generation. A glider travels one cell diagonally every four generations. If your blinker does anything else, your code has a bug, and you know immediately. That makes the Game of Life a better test bed than most beginner games, where "something looks a bit off" is hard to pin down.

The three bugs, in the order students hit them

The stripe: one row, copied many times

The tempting way to make a grid of zeros is to write a row of zeros and multiply the outer list, something like [[0] * width] * height. It prints exactly what you expect. But the outer multiplication does not make new rows. It makes a list containing the same row object, height times. Change grid[3][7] and you change column 7 in every row, because every row is the same row. That is the stripe.

The fix is to build each row separately with a list comprehension, so the outer loop creates a fresh list each time: [[0] * width for _ in range(height)]. The inner multiplication is safe because integers cannot be changed in place; the outer one is not. I ask students to prove this to themselves by printing id() of row 0 and row 1 for both versions. Seeing the same number twice does more than my explanation ever does.

The smear: updating while you are still reading

Once the grid is fixed, the next generation usually looks wrong in a quieter way. Blinkers fade out, gliders fall apart after two steps. The cause is updating cells in the same grid you are counting neighbours from. When the loop reaches cell (5, 5), its neighbour at (5, 4) has already been changed for the next generation, so the count mixes two generations together.

The rules assume every cell changes at the same instant. The code has to fake that by reading from the old grid and writing into a new one, then swapping them at the end. Students often try copying the grid with grid.copy(), which brings the first bug back in a new form, because a shallow copy of a list of lists still shares the inner rows. Building the new grid fresh with a comprehension each generation avoids that.

The edge that only breaks on one side

This one is my favourite to watch students discover. When counting neighbours of a cell in column 0, the code looks at column -1. In Python, index -1 is the last item, so no error appears: the left edge quietly reads from the right edge. Then at the last column, the code looks at column width, which does not exist, and the program crashes with an IndexError.

So a beginner sees a crash only on the right and bottom edges, and assumes the left and top are fine. They are not fine, they are wrong in silence. In a session last term, seven of the nine students who reached this stage reported the right edge crash, and none had noticed the left edge reading the wrong side.

The fix is a choice. Either check bounds explicitly and treat outside cells as dead, or wrap both edges on purpose with the modulo operator so the grid behaves like a doughnut. Both are valid versions of the game. What matters is deciding, instead of inheriting half a wrap by accident.

Why I do not hand out a template

Many coding clubs give students a starter file with the grid already built correctly, so they can "focus on the rules". I see the appeal, and I did this myself for a while. I stopped because the students who got the template sailed through the Game of Life and then hit the aliasing bug months later in a contest, in a harder problem, with a timer running. The ones who built their own broken grid first recognised the stripe straight away the next time it appeared.

A bug you meet in a toy is a lesson. The same bug in an exam is just a lost hour.

I do give out one thing: a file with three known patterns as test cases, a blinker, a block that never changes, and a glider, with their expected positions after four generations. That turns "does it look right?" into "does it pass?", which is the habit I want contest students to build anyway.

What else the project teaches

Beyond the three bugs, the Game of Life is a gentle way into nested loops and helper functions. A count_neighbours function that takes a grid and a position is easy to test by itself, which leads naturally to writing tests for other things. If your group enjoyed this, the loop practice in how game logic teaches loops and conditions is a good next step, and tips for debugging your first game projects covers the wider habits.

One exercise for this week

Write the two grid lines side by side, the multiplied version and the comprehension version, set one cell in each, and print both grids. Then print the id() of the first two rows of each. It takes ten minutes and you will never write the broken version again without noticing. More small projects are waiting in our games section.

SD
Selene Dragomir

Selene coaches students for programming contests. She writes about practice problems, debugging and the habits that help beginners get unstuck on their own.

More posts by Selene

More in Games

01doorganico 084goad 3hot8 4465456 after5internprogram ahandfulofblessings ahmadpc ahomcybersecurity allodialresources annodex antiquesandperiodcollectibles aotc asianworldkashmir beedlectrics bloggertemplatefree bluefeatheragency bodyweightsolution campaign08blog carfixadvisor catstreetcollective cbstvsports cnfanspurchasing committeeof500yearsofdignityandresistance covertopsdjs cs-kaiyuangame cuestacomunicaciontotal cxoutlet dancebetgame deskgearhub destinyvillastay detectmagazine di33 diamondsteelbuildings donmo dyanadropship emarsysfinance emimattl escapechan eskitarih evautoplay everythingweddings-orlando farmzone feedviewerapp foxstudiosscratch gamevui123 gleasongrade greetingsday hacklabasu haskell iw-kaiyun jagoanproperti jilidskripsi joltbusinessfinance juegodepokergratis juegospucca kaeru-festival latestmehndidesigns lestanzediottaviano litemagazines localcommunityhero lustrsazan mariashebets menswheels newwebstechs1 nikkit ootheday oxigenvpn plurfitness prefetial probudgetcarpetcleaning psychicmrsmartin pyaten qvantor rejoignezlogis rexzbot riselayouts ruchin rwxqsgd5 sakaltime shibiaowang shop-bot skrtips tetapatrium topselller uneplanteartificielle vipertechcr webtech1223411 wo-kygaming yamaha-mekar yeknava yoybuysheets zenunwa yamaha-mekar jilidskripsi ankertoto ankertoto ankertoto ankertoto