Computer Science • Algorithms

The Algorithms Behind Match-3 Games

Written by Sudhishkumar K • Dec 2026 • 20 Min Read

Match-3 games are deceptively simple. To the player, it's just colorful candy falling on a screen. But to a developer, a game like Candy Crush or Royal Match is a complex orchestration of array manipulation, recursive search algorithms, and state management.

If you try to build a Match-3 game using Unity's physics engine (Rigidbodies and Colliders), you will fail. The physics will be unpredictable, the matching logic will be buggy, and performance will tank on mobile devices.

Professional Match-3 games are Deterministic. They don't rely on physics; they rely on grid math. In this guide, we will break down the core pillars of Match-3 architecture: The Data Structure, Match Detection, Gravity, and Deadlock Prevention.

1. The Model-View-Controller (MVC) Approach

The most important rule in puzzle game development is identifying the separation between data and visuals.

When a player swaps two gems, we don't swap the GameObjects first. We swap the numbers in the array. Then, the View updates to match the array.

The Grid Structure

We usually use a simple 2D array. Integers are faster and lighter than classes.


public class Board {
    // 0 = Empty/Air
    // 1 = Red, 2 = Blue, 3 = Green, 4 = Yellow, 5 = Purple
    // 6 = Bomb, 7 = Rocket
    public int width = 8;
    public int height = 8;
    public int[,] grid;

    public void Init() {
        grid = new int[width, height];
    }
}
            

2. Detecting Matches (The Sweep)

There are two types of match detection: Targeted and Global.

A. Targeted Swap Check

When a player swaps Gem A at `(x, y)` with Gem B at `(x+1, y)`, we only need to check matches originating from those two specific coordinates. We check horizontally and vertically.


bool CheckMatchesAt(int x, int y) {
    int currentType = grid[x, y];
    if (currentType == 0) return false;

    // Horizontal Check
    if (x > 0 && x < width - 1) {
        if (grid[x - 1, y] == currentType && grid[x + 1, y] == currentType)
            return true;
    }

    // Vertical Check
    if (y > 0 && y < height - 1) {
        if (grid[x, y - 1] == currentType && grid[x, y + 1] == currentType)
            return true;
    }
    
    return false;
}
            

B. Global Board Scan

After matches are cleared and new gems fall in (Gravity), we must scan the entire board for "Chain Reactions." This is usually done by iterating through every cell and checking for 3 consecutive IDs.

3. The "Toon Blast" Algorithm: Flood Fill

Some games (like Toon Blast or Pet Rescue Saga) don't use swapping. You tap a group of blocks to destroy them. This requires the Flood Fill algorithm (specifically, Connected Component Labeling).

This is a classic recursive search (Depth First Search).


// Returns a list of all connected matching blocks
List GetConnectedGroup(int startX, int startY) {
    int targetColor = grid[startX, startY];
    List matches = new List();
    
    // We need to keep track of visited cells to prevent infinite loops
    bool[,] visited = new bool[width, height];

    // Recursive helper function
    FloodFill(startX, startY, targetColor, visited, matches);
    
    return matches;
}

void FloodFill(int x, int y, int color, bool[,] visited, List matches) {
    // Boundary Checks
    if (x < 0 || x >= width || y < 0 || y >= height) return;
    if (visited[x, y]) return;
    if (grid[x, y] != color) return;

    visited[x, y] = true;
    matches.Add(new Vector2Int(x, y));

    // Search Neighbors (Up, Down, Left, Right)
    FloodFill(x + 1, y, color, visited, matches);
    FloodFill(x - 1, y, color, visited, matches);
    FloodFill(x, y + 1, color, visited, matches);
    FloodFill(x, y - 1, color, visited, matches);
}
            

Recursion Warning

Recursion is elegant but can cause a "Stack Overflow" on massive boards (e.g., 100x100). For extremely large grids, convert this to an Iterative approach using a Stack<Vector2Int> data structure instead of function calls.

4. Gravity Logic (Falling Gems)

Once matches are found, we set those grid cells to `0` (Empty). Now we must make the gems above them fall.

Do NOT use physics. Physics is expensive. Use a Column-based shift algorithm.

  1. Iterate through each column from bottom (`y = 0`) to top.
  2. If we find an empty cell (`0`), look above it for the first non-empty cell.
  3. Move that non-empty cell down to the empty slot.
  4. Repeat until all empty slots are at the top of the column.
  5. Spawn new random gems in the empty slots at the top.

5. Deadlock Detection (Is the game over?)

How does the game know to show the "No Moves Left" popup? It doesn't guess. It simulates.

Every time the board settles, the game runs a "Virtual Swap" simulation in the background. It brute-forces every possible move on the board.

Step 1: Iterate Loop through every cell (x, y) on the board.
Step 2: Virtual Swap For each cell, pretend to swap it with its Right neighbor. Does it create a match of 3?
Step 3: Check Vertical Pretend to swap it with its Bottom neighbor. Does it create a match of 3?
Step 4: Result If ANY swap returns true, stop immediately. The game continues. If the loop finishes with zero trues, trigger "Shuffle Board."

6. Performance Optimization

Mobile devices have limits. Here is how to keep your Match-3 game running at 60 FPS.

Object Pooling

In a typical level, you might destroy 500 gems. If you use `Destroy(gameObject)` and `Instantiate(prefab)` 500 times, the Garbage Collector (GC) will cause lag spikes.
Instead, when a gem matches, disable it (`SetActive(false)`) and move it to a "Pool" list. When you need a new gem at the top, take one from the Pool, reset its position, and enable it.

Dirty Flags

Do not redraw the entire board every frame. Use a `bool isDirty` flag.
Only run your visual update logic if `isDirty == true`. The logic sets the flag to true whenever a swap happens or gravity finishes.

Conclusion

Match-3 games are a perfect example of how complex logic can be hidden behind simple interactions. The player sees exploding candy; the developer sees a beautifully optimized 2D array utilizing recursion and predictive algorithms.

At Boomie Studio, we use these exact algorithms for our titles like Hexa Jam. By mastering the grid, you master the genre.


Frequently Asked Questions (FAQ)

Q: Should I use a 1D array or 2D array?
A: A 1D array (`int[] grid`) is technically faster in memory due to cache locality, but a 2D array (`int[,]`) is much easier to read and debug. For a board size of 8x8 or 12x12, the performance difference is negligible. Stick to 2D for simplicity.

Q: How do I handle special matches (e.g., L-Shape or T-Shape bombs)?
A: When a match is detected (e.g., 5 gems), check the shape of the match coordinates. If the matched list contains both a horizontal set of 3 and a vertical set of 3 sharing a central gem, instantiate a Bomb instead of clearing them.