How I Programmed the Game of Life in a Google Sheet with Google Apps Script
A Google Sheet, being essentially a two-dimensional array, is perfect for this task!

What Is the Game of Life?
According to Wikipedia, the Game of Life “is a cellular automaton devised by the British mathematician John Horton Conway in 1970.”
It begins on a two-dimensional grid of square cells. Each cell can be either alive or dead. Every cell interacts with its eight immediate neighbors. A live cell only remains alive if it has two or three living neighbors. If it has fewer than two living neighbors, it dies as if by underpopulation. Conversely, if it has more than three, it dies as if by overpopulation. A dead cell remains dead unless it has exactly three living neighbors; otherwise, it becomes a live cell, as if by reproduction.
By simultaneously recalculating the state of the cells, we can create a new generation, and another one, and so on. In some cases, the cells all ultimately die, whereas in other cases, they do not. Along the way, they might even form some interesting patterns (more on them later).
With this simple set of rules, we are looking at a simulation that plays out like this:

There is no immediate practical use for the Game of Life in a spreadsheet; however, it is a fun algorithmic challenge. Moreover, Google Sheets natively provides us with the perfect data structure: a two-dimensional array. This is all the more reason to work on those array skills!
As usual, there is a GitHub repo with the full source code. Alternatively, you can just make a copy of this spreadsheet.
The Approach
We will be using the OOP style and ES6 syntax extensively. Although I did try FP, it seemed to lend itself a little less well than OOP in this specific challenge. I am also a fan of using WeakMap
s as private or read-only properties for JavaScript classes. I recognize that the latest version of JS supports private properties directly, but hey, one step at time, right? I also like to enclose my classes in IIFEs that return the actual class and hide away all of the private properties and methods.
Our classes diagram looks like this:

You will notice that our main class GameOfLife
contains a Grid
that is composed of Cell
s.
We will also be using a 25x25 grid that we will need to set up manually. Although you can automate this on your own if you prefer, it is not the objective of this article. We will use the setBackgrounds()
method to color the alive and dead cells.
Now, let’s jump into the code.
The Code
We will look into the classes in the order in which they appear: first GameOfLife
, then Grid
, then Cell
. Ultimately, we will specifically look into our render()
function that displays the results of our app’s calculations.
GameOfLife Class
The constructor of GameOfLife
accepts two parameters: renderCallback
and options
.
The former is, as the name suggests, a callback that will be used to render a generation of cells on the grid. Having it passed as a callback rather than building it in has the advantage of making this library extensible, meaning that it respects the open/closed principle. This allows any other developers to use their own rendering logic (e.g., inserting values rather than changing cell backgrounds).
The options
parameter is an object that contains the following optional properties:
width
: number, the width of the grid, defaults to 25height
: number, the height of the grid, defaults to 25patternName
: string, in case we want to initiate the grid with a specific patternframeMs
: number, how long to pause in milliseconds before rendering a new generation, defaults to 100numIterations
: number, how many generations to calculate and display on the grid (you probably want to stay within the six-minute or thirty-minute execution threshold, depending on the type of your account)
The private _grid
property is an instance of the Grid
class that we will examine next. Some of the options, namely width
, height
, and patternName
, which we pass to GameOfLife
, are passed on to the Grid
instance.
The game starts when the play()
method is called. It’s a recursive method that pauses the game a predefined number of milliseconds. It then asks the Grid
to create a new generation of cells via the Grid.nextGen()
method and renders them. It stops after a predefined number of times. Additionally, Grid.nextGen()
can return null
in case there are no changes between generations. In this case, the game stops as there is no longer a need to run any more calculations. It also calls the render()
method that calls the render
callback.
The full source code for the class is provided below.

Grid Class
The Grid
class’ constructor accepts the options
object as a parameter that contains width
, height
, and patternName
entries and stores them as read-only properties. It also has the grid
property that is a two-dimensional array of instances of the Cell
class. Granted, I recognize that Grid.grid
is not the best name, so if you have a better idea of what this property can be renamed to, let me know in the comments. I’m all ears!
The class has a few methods:
setCell()
sets the value of a specific cell in the grid
property to a given Cell
instance. It is used when initiating or calculating a new generation of cells and setting new values to the grid.
init()
creates a new grid
that is a two-dimensional array with the private _make2DArray()
method with random alive and dead cells. Later, we will modify this method to allow setting specific patterns. From the start, we’re building a two-dimensional array and will be storing our cell data there. This will allow us to render the grid easily.
nextGen()
calculates a new generation of cells. It loops through the whole grid, calls the getNewIsAlive()
method of the cell in question, and sets that value to a new Grid
instance. Once all cells have been covered, that new Grid
instance is returned.
Here is the full source code of the Grid
class:

Cell Class
The Cell
’s constructor is called by the Grid
class when a grid is built. The constructor accepts the following parameters: Grid
, row
, column
, and isAlive
. It is the Grid
class that sets the value of isAlive
to true
or false
; however, it also passes its own reference as well as the row and column index to the cell. This is necessary for the cell to count its alive neighbors and return its new state. Then, the grid can create a new Cell
based on the current cell’s future state.
The neighborsAlive
property and getNewIsAlive()
method are what implement the actual rules of the Game of Life. neighborsAlive
returns the number of alive neighbor cells for the current cell. If a cell is on the edge of the grid, it wraps around to check the cells on the opposite side of the grid. This property is then used in getNewIsAlive()
to calculate the future state of the cell depending on whether it is currently alive or dead. If you remember, this method is used in Grid.nextGen()
iteratively to create a whole new generation.
The full source code for the Cell
class is provided below:

render Callback
The render
callback accepts the Grid.grid
property, which, if you remember, is a two-dimensional array of Cells
and replaces them with hex color codes depending on whether Cell.isAlive
returns true
or false
. Then, it applies the new two-dimensional arrays to the spreadsheet as background colors with setBackgrounds()
.
Notice that I am storing the Range
reference in a Singleton to avoid calling SpreadsheetApp.getActive().getRange()
on every iteration. This improves the function’s performance.

The Different Patterns
As previously mentioned, there are several types of patterns depending on how they behave. Let’s review a few different types and then implement them in our code.
Spaceships
Spaceships move across the grid and never die. Below is a glider, which is a one example of a spaceship.

Oscillators
Oscillators return to their initial state after a finite number of generations. Below is an example of an oscillator called Penta-decathlon (period 15)

Die-Hard
Die-hards eventually disappear rather than stabilizing.
Still Lifes
Still lifes do not change from one generation to the next.
Pattern Implementation
First, let’s define a pattern
object and create a few patterns with zeros and ones in a two-dimensional array as follows:

Of course, ones represent alive cells and zeros represent dead ones. Now that we have a simple way to describe our patterns, let’s add a new private method to our Grid
that will insert them into the defined grid size. This method should read the width and height of the grid and insert the pattern in the middle. This is what I came up with:

Now, we just need to add one line to our Grid.init()
method to return the pattern if there is a patternName
present in the options
object instead of randomizing, as follows:

Running the Game of Life
Running the Game of Life in our spreadsheet is now simply a matter of creating an instance of GameOfLife
with the right parameters and calling its play()
method:

Conclusion
If you have read until here, thank you very much. I certainly hope that you found the information useful! This is just another example of how versatile spreadsheets are and the multitude of things you can do with with a two-dimensional array data structure and the OOP approach.
If you enjoyed this article, feel free to buy me a coffee — it’s what fuels my code. :)
About the Author
Dmitry Kostyuk is a full-time GAS developer and the Founder of Wurkspaces.dev.