Introduction to Quadtrees
A quadtree is a tree data structure in which each internal node has exactly four children. It is used to partition a two‑dimensional space by recursively subdividing it into four quadrants or regions. Originally devised for spatial indexing and image compression, quadtrees have become a staple topic in technical interviews, especially at companies that work with mapping, computer graphics, game engines, and geospatial data.
In an interview setting, quadtree problems test your ability to apply recursion, divide‑and‑conquer strategies, and tree construction/manipulation on non‑standard trees. They also reveal how well you can handle coordinate logic and boundary conditions in a two‑dimensional plane. This guide covers everything you need: what quadtrees are, why they matter, how to use them effectively in problem‑solving, best practices, and a complete walkthrough of a classic interview problem with production‑ready code.
Understanding Quadtree Fundamentals
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →What is a Quadtree?
A quadtree represents a hierarchical decomposition of a 2D region. The root covers the entire region. If a region requires further subdivision (because it contains heterogeneous data, exceeds a capacity threshold, or simply needs to be split), it is divided along the x‑ and y‑axes into four equal quadrants: top‑left, top‑right, bottom‑left, and bottom‑right. Each quadrant becomes a child node, and the process repeats recursively.
The two most common variants you’ll encounter are:
- Point‑Region (PR) Quadtree: Stores points or objects. A node splits when its capacity is exceeded, distributing points among the four quadrants. Used for spatial indexing and collision detection.
- MX Quadtree: Partitions a discrete grid (often of 0s and 1s). A node becomes a leaf if all cells in its region have the same value; otherwise it splits. This is the basis of many interview problems like “Construct Quad Tree” (LeetCode 427).
Structure and Variants
A typical quadtree node contains:
val– the value for a leaf (e.g., 0 or 1) or an arbitrary value for an internal node.isLeaf– a boolean indicating whether the node is a leaf (no children).topLeft, topRight, bottomLeft, bottomRight– references to the four child nodes (null if leaf).
Other variants exist, like PR quadtrees with a bucket capacity and spatial boundaries, or point quadtrees where each node stores a single point and the quadrants are defined relative to that point. For interviews, stick to the grid‑based MX variant unless the problem explicitly mentions dynamic point insertion.
Why It Matters for Interviews
Quadtree problems appear frequently because they blend multiple core competencies:
- Recursion and Divide‑and‑Conquer: Building and querying a quadtree forces you to decompose a problem into smaller identical subproblems.
- 2D Spatial Reasoning: You must handle coordinate mappings, boundary calculations, and quadrant assignment correctly.
- Tree Construction and Traversal: It’s a non‑binary tree with four children, testing your comfort with custom node definitions and recursion over multiple branches.
- Optimization Awareness: Many solutions can be optimized from O(n²) brute force to O(n log n) or better using quadtrees.
Companies like Google (maps), Uber (geospatial indexing), gaming studios (collision detection), and Adobe (image processing) love these problems. Mastering quadtrees signals strong algorithmic maturity.
How to Use Quadtrees in Problem Solving
Common Interview Problems
The most frequently assigned quadtree challenges include:
- Construct Quad Tree from a binary grid (LeetCode 427) – Build an MX quadtree from a 2D array of 0/1.
- Quadtree Intersection (LeetCode 558) – Compute the logical OR of two quadtrees representing binary images.
- Range Query / Spatial Search – Given a PR quadtree, return all points within a rectangular region.
- Collision Detection – Use a quadtree to efficiently find all pairs of intersecting objects.
- Image Compression / Representation – Encode a high‑resolution image as a quadtree and prune redundant subtrees.
- Neighbor Finding – Given a leaf node, find its adjacent leaves in the same tree.
Implementation Strategies
Representing Nodes
Start by defining a clean node class. For MX quadtrees (grid problems), use the standard definition with val, isLeaf, and four child pointers. For PR quadtrees, include bounding box coordinates and a list or bucket of points.
class Node:
def __init__(self, val, isLeaf, topLeft=None, topRight=None, bottomLeft=None, bottomRight=None):
self.val = val # 0 or 1 for leaf, 0 or 1 for internal (often 0)
self.isLeaf = isLeaf
self.topLeft = topLeft
self.topRight = topRight
self.bottomLeft = bottomLeft
self.bottomRight = bottomRight
Building a Quadtree from a Grid
This is the cornerstone pattern. The idea: given a square subgrid defined by top‑left coordinates (row, col) and size, check if all cells have the same value. If yes, return a leaf node with that value. Otherwise, split into four quadrants of half size and recursively build the children. Return an internal node with isLeaf=False and the four children set.
def build_quadtree(grid):
n = len(grid)
def all_same(r, c, size):
val = grid[r][c]
for i in range(r, r + size):
for j in range(c, c + size):
if grid[i][j] != val:
return False
return True
def construct(r, c, size):
if all_same(r, c, size):
return Node(grid[r][c], True)
half = size // 2
return Node(0, False,
construct(r, c, half),
construct(r, c + half, half),
construct(r + half, c, half),
construct(r + half, c + half, half))
return construct(0, 0, n)
The time complexity is O(n²) in the worst case (a checkerboard pattern forces full decomposition) because at each level we scan all cells of the current region. However, the recursion depth is O(log n), and for many inputs early leaf detection prunes work.
Performing Range Queries
For a PR quadtree storing points, a range query traverses the tree and only descends into quadrants that intersect the query rectangle. This prunes the search space dramatically compared to a linear scan.
class PRQuadNode:
def __init__(self, x, y, width, height, capacity=4):
self.x = x # center or top-left of region
self.y = y
self.width = width
self.height = height
self.points = [] # bucket of (px, py)
self.capacity = capacity
self.children = [None, None, None, None] # TL, TR, BL, BR
def range_query(node, rect_x, rect_y, rect_w, rect_h):
if node is None:
return []
result = []
# Check each point in this node's bucket
for (px, py) in node.points:
if rect_x <= px < rect_x + rect_w and rect_y <= py < rect_y + rect_h:
result.append((px, py))
# Recurse into children if they intersect the query rectangle
if node.children[0] is not None: # internal node
half_w = node.width / 2
half_h = node.height / 2
# Determine which quadrants intersect and recurse
if rect_x < node.x + half_w and rect_y < node.y + half_h: # TL
result.extend(range_query(node.children[0], rect_x, rect_y, rect_w, rect_h))
if rect_x + rect_w > node.x + half_w and rect_y < node.y + half_h: # TR
result.extend(range_query(node.children[1], rect_x, rect_y, rect_w, rect_h))
if rect_x < node.x + half_w and rect_y + rect_h > node.y + half_h: # BL
result.extend(range_query(node.children[2], rect_x, rect_y, rect_w, rect_h))
if rect_x + rect_w > node.x + half_w and rect_y + rect_h > node.y + half_h: # BR
result.extend(range_query(node.children[3], rect_x, rect_y, rect_w, rect_h))
return result
Compressing and Pruning
Many quadtree problems (e.g., intersection, logical OR, image compression) involve post‑processing the tree to merge identical leaves. The technique: perform a bottom‑up traversal; if all four children are leaves with the same value, replace the internal node with a leaf of that value. This reduces the tree size and is essential for producing canonical representations.
def compress(node):
if node.isLeaf:
return node
# First compress all children
node.topLeft = compress(node.topLeft)
node.topRight = compress(node.topRight)
node.bottomLeft = compress(node.bottomLeft)
node.bottomRight = compress(node.bottomRight)
# If all children are leaves with same value, merge
if (node.topLeft.isLeaf and node.topRight.isLeaf and
node.bottomLeft.isLeaf and node.bottomRight.isLeaf and
node.topLeft.val == node.topRight.val == node.bottomLeft.val == node.bottomRight.val):
return Node(node.topLeft.val, True)
return node
Step-by-Step Problem Solving Approach
When you face a quadtree problem in an interview, follow this systematic method:
- 1. Clarify the variant: Is it an MX quadtree on a grid, or a PR quadtree with dynamic points? Do you have a predefined node class?
- 2. Define the recursion contract: For grid problems, decide the parameters (top‑left coordinates, size) and what the function returns (a Node).
- 3. Handle the base case: Determine when a region is homogeneous (leaf). This often involves scanning the subgrid or checking a bucket capacity.
- 4. Recursive decomposition: Compute half‑size or quadrant boundaries. Make four recursive calls. Build an internal node with those children.
- 5. Post‑processing: If the problem requires compression or merging, apply a bottom‑up pass after construction.
- 6. Test with edge cases: Single‑cell grid, all same values, checkerboard, non‑square regions if allowed, maximum recursion depth.
Best Practices and Optimization Tips
Choosing the Right Variant
Don’t blindly use an MX quadtree for everything. If you’re dynamically inserting points, a PR quadtree with a bucket capacity avoids infinite subdivision. For static grids, MX is simpler and sufficient. For collision detection among moving objects, a PR quadtree rebuilt each frame (or a loose quadtree) is preferred. Always confirm the expected operations with your interviewer.
Handling Edge Cases
- Non‑square regions: Some quadtree implementations support rectangular regions. Ensure your half‑size logic uses width/2 and height/2 separately. Never assume square unless explicitly stated.
- Empty regions: If a quadrant contains no data (e.g., no points in a PR quadtree), return
Noneor a dedicated empty leaf. Don’t create unnecessary internal nodes. - Maximum depth: For PR quadtrees with high point density, set a maximum depth to prevent stack overflow. At that depth, force a leaf regardless of capacity.
- Integer overflow in coordinates: Use integer division carefully; for odd sizes, decide on a consistent flooring strategy (e.g., left quadrants get the smaller half).
Performance Considerations
- Time Complexity: Building an MX quadtree from an n×n grid is O(n²) in the worst case (each cell visited at each level), but often much less in practice. Range queries on a balanced PR quadtree with k points can be O(log n + m) where m is the number of reported points.
- Space Complexity: A full quadtree for a grid can have up to O(n²) nodes in the worst case (e.g., checkerboard), which is memory‑heavy. Compression reduces this drastically.
- Recursion Depth: Max depth is log₂(n) for an n×n grid. Use iterative approaches if you hit language recursion limits for huge inputs (rare in interviews).
- Early Termination: In
all_samechecks, break early on mismatch. This significantly speeds up construction for typical inputs.
Sample Interview Problem Walkthrough: Construct Quad Tree (LeetCode 427)
This is the most iconic quadtree interview problem. You’re given an n×n binary grid of 0s and 1s where n is a power of 2. Construct an MX quadtree representing the grid according to the definition: a leaf has isLeaf=True and val equal to the uniform value of its region; an internal node has isLeaf=False and can have any val (commonly 0 or 1), with four children exactly covering the four sub‑quadrants.
The solution directly applies the building pattern described earlier. Below is the complete, ready‑to‑run Python implementation.
# Definition for a QuadTree node.
class Node:
def __init__(self, val, isLeaf, topLeft=None, topRight=None, bottomLeft=None, bottomRight=None):
self.val = val
self.isLeaf = isLeaf
self.topLeft = topLeft
self.topRight = topRight
self.bottomLeft = bottomLeft
self.bottomRight = bottomRight
class Solution:
def construct(self, grid):
n = len(grid)
# Helper to check if a subgrid is homogeneous
def is_uniform(r, c, size):
base = grid[r][c]
for i in range(r, r + size):
for j in range(c, c + size):
if grid[i][j] != base:
return False
return True
# Recursive construction
def build(r, c, size):
if is_uniform(r, c, size):
return Node(grid[r][c], True)
half = size // 2
return Node(0, False,
build(r, c, half), # topLeft
build(r, c + half, half), # topRight
build(r + half, c, half), # bottomLeft
build(r + half, c + half, half)) # bottomRight
return build(0, 0, n)
Complexity Analysis
Time Complexity: In the worst case (alternating 0/1 like a chessboard), every subgrid except 1×1 requires scanning all its cells. The work at each level sums to O(n²) leading to O(n²) total time. In the best case (all cells same), it’s O(1) because the root scan detects uniformity and stops. For random grids, performance lies between these extremes.
Space Complexity: The tree can have up to O(n²) nodes in the worst case, each node being a constant‑size object. The recursion stack uses O(log n) space.
Optimization Note: You can avoid scanning the entire subgrid for uniformity by passing the expected value from the parent, but in interviews the straightforward scanning is acceptable and clear.
Conclusion
Quadtrees are a powerful tool for partitioning two‑dimensional space, and they appear regularly in technical interviews to gauge your recursion, tree manipulation, and spatial reasoning skills. By internalizing the core patterns—constructing from a grid, querying ranges, compressing trees, and handling edge cases—you can confidently tackle any quadtree‑based problem. Start by clarifying the variant, design a clean recursive decomposition, and always test on edge cases. The provided code templates and walkthrough give you a solid foundation to adapt and extend during real interview scenarios. With practice, quadtree problems become an opportunity to showcase elegant, efficient problem‑solving.