Optimizing Conway's 'Game of Life'
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Conway's Game of Life has simple rules, but a naive implementation becomes slow quickly on large boards or long simulations. The main optimization question is not the rules themselves. It is how to avoid recomputing work for cells that obviously cannot change.
Start With the Cost Model
A naive implementation checks every cell and recounts all eight neighbors on every generation. On an n x m grid, that means scanning the entire board even when only a tiny region is active.
The classic rules are:
- a live cell survives with 2 or 3 live neighbors
- a dead cell is born with exactly 3 live neighbors
- all other cells are dead next generation
The first major optimization is double buffering so reads and writes do not interfere.
This is correct, but still wasteful for sparse worlds.
Sparse Sets Are Often Faster
If most cells are dead, store only live cells. Then the only cells that can change next turn are the live cells and their neighbors.
For patterns with lots of empty space, this avoids scanning irrelevant coordinates entirely.
Dense Optimizations Still Matter
If your board is mostly full, sparse sets lose their advantage. In that case, better dense-grid techniques include:
- contiguous memory layouts instead of nested objects
- bitsets or packed bytes instead of Python integers per cell
- vectorized operations with NumPy or GPU kernels
- avoiding bounds checks by using padded borders
For example, NumPy can compute neighbor totals with shifted slices far faster than pure Python loops.
Avoid Unnecessary Allocation
Repeatedly creating new grids can be expensive. Reusing two buffers and swapping references each generation is often faster than allocating from scratch.
That matters most in low-level languages, but it helps in higher-level ones too because it reduces pressure on the allocator and garbage collector.
Special-Purpose Algorithms
For very large sparse patterns and deep time jumps, Hashlife is the classic advanced optimization. It memoizes recursive regions of the board so repeated subpatterns do not need to be recomputed.
Hashlife is not the first optimization to implement, though. It is powerful but much more complex than a good dense or sparse stepper.
In practice, most projects should first choose the right representation:
- dense array for compact, active boards
- sparse set or hash map for mostly empty infinite boards
That choice usually matters more than micro-optimizing individual loops.
Benchmark the Right Workload
A glider gun on an infinite sparse plane behaves very differently from a random 2048 by 2048 board. Measure performance on the kind of pattern your application actually uses.
It is easy to celebrate an optimization that helps one benchmark and hurts the real workload.
Common Pitfalls
A common mistake is mutating the current board in place while still using it to count neighbors. That changes the rules mid-generation and produces wrong results.
Another mistake is assuming sparse storage is always faster. If the board is dense, hash-based bookkeeping can cost more than array scans.
Developers also spend time on low-level tweaks before choosing the right representation. Data structure choice dominates performance here.
Summary
- Naive Game of Life recomputes too much work on every generation.
- Use double buffering so updates do not corrupt neighbor counts.
- Sparse sets are effective when most cells are dead.
- Dense boards benefit from contiguous storage, vectorization, and buffer reuse.
- Choose the representation that matches the workload before chasing micro-optimizations.

