meshgrid & Coordinate Grids

Reviewed & published by Brayan K

np.meshgrid turns two 1D coordinate vectors into a pair of 2D coordinate arrays that name every point on a rectangular grid, so you can evaluate a function of two variables f(x, y) everywhere at once without loops.

Part of the free NumPy course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You'll build grids from x and y vectors, evaluate surfaces in a single vectorized expression, compare indexing='xy' with indexing='ij', and use sparse=True to save memory.

What You'll Learn in This Lesson

1 From Two Vectors to a Grid of Coordinates

Suppose you have an x-axis [0, 1, 2] and a y-axis [0, 1]. Together they describe a grid of 6 points. np.meshgrid(x, y) hands you two arrays of the same shape: X repeats the x-values across the rows, and Y repeats the y-values down the columns. Read off the same position in both arrays and you get the coordinates of one grid point.

import numpy as np

x = np.array([0, 1, 2])
y = np.array([0, 1])

X, Y = np.meshgrid(x, y)

# X = [[0 1 2]      Y = [[0 0 0]
#      [0 1 2]]          [1 1 1]]
# X[i, j], Y[i, j] is the (x, y) of grid point (i, j)
import numpy as np

x = np.array([0, 1, 2])
y = np.array([0, 1])

X, Y = np.meshgrid(x, y)

print(X)
# [[0 1 2]
#  [0 1 2]]

print(Y)
# [[0 0 0]
#  [1 1 1]]

print(X.shape, Y.shape)   # (2, 3) (2, 3)

# ✅ Expected output:
# [[0 1 2]
#  [0 1 2]]
# [[0 0 0]
#  [1 1 1]]
# (2, 3) (2, 3)

2 Evaluating f(x, y) Over the Whole Grid

The reason meshgrid exists is so you can evaluate a function at every grid point in one shot. Because X and Y are arrays, any element-wise expression like X**2 + Y**2 is computed for every cell at once, producing a 2D surface of values. This is exactly the data a 3D surface plot or a heatmap needs.

X, Y = np.meshgrid(x, y)
Z = np.sqrt(X**2 + Y**2)   # distance from origin at every point
import numpy as np

x = np.linspace(-2, 2, 5)   # [-2 -1  0  1  2]
y = np.linspace(-2, 2, 5)

X, Y = np.meshgrid(x, y)

# Evaluate f(x, y) = x^2 + y^2 everywhere at once
Z = X**2 + Y**2

print(Z)
# [[8. 5. 4. 5. 8.]
#  [5. 2. 1. 2. 5.]
#  [4. 1. 0. 1. 4.]
#  [5. 2. 1. 2. 5.]
#  [8. 5. 4. 5. 8.]]

print("Minimum value:", Z.min())   # Minimum value: 0.0

# ✅ Expected output:
# [[8. 5. 4. 5. 8.]
#  [5. 2. 1. 2. 5.]
#  [4. 1. 0. 1. 4.]
#  [5. 2. 1. 2. 5.]
#  [8. 5. 4. 5. 8.]]
# Minimum value: 0.0

3 indexing='xy' vs 'ij', and sparse=True

Two keyword arguments tune meshgrid. indexing controls which input varies along which axis. The default 'xy' is Cartesian and gives an output shape of (len(y), len(x)) — ideal for plotting. Switching to 'ij' gives matrix-style indexing with shape (len(x), len(y)). Separately, sparse=True returns broadcastable arrays instead of full grids to save memory.

x = np.array([0, 1, 2])   # length 3
y = np.array([0, 1])      # length 2

Xxy, Yxy = np.meshgrid(x, y)                 # shape (2, 3)  ->  (len(y), len(x))
Xij, Yij = np.meshgrid(x, y, indexing="ij")  # shape (3, 2)  ->  (len(x), len(y))

# Sparse grids broadcast but store far fewer numbers
Xs, Ys = np.meshgrid(x, y, sparse=True)
# Xs.shape == (1, 3), Ys.shape == (2, 1)
import numpy as np

x = np.array([0, 1, 2])
y = np.array([0, 1])

Xxy, Yxy = np.meshgrid(x, y)                 # default 'xy'
Xij, Yij = np.meshgrid(x, y, indexing="ij")  # matrix 'ij'

print("xy shape:", Xxy.shape)   # xy shape: (2, 3)
print("ij shape:", Xij.shape)   # ij shape: (3, 2)

# sparse grids still broadcast correctly
Xs, Ys = np.meshgrid(x, y, sparse=True)
print("sparse:", Xs.shape, Ys.shape)   # sparse: (1, 3) (2, 1)
print(Xs + Ys)
# [[0 1 2]
#  [1 2 3]]

# ✅ Expected output:
# xy shape: (2, 3)
# ij shape: (3, 2)
# sparse: (1, 3) (2, 1)
# [[0 1 2]
#  [1 2 3]]

🎯 YOUR TURN: Fill in the Blank

Replace the ___ with the function that builds coordinate grids, then evaluate a simple product surface.

import numpy as np

x = np.array([1, 2, 3])
y = np.array([10, 20])

# Build the coordinate grid (fill in the function name)
X, Y = np.___(x, y)

# Multiply matching coordinates everywhere at once
product = X * Y

print(product)
# Expected output:
# [[10 20 30]
#  [20 40 60]]

Answer: meshgrid. X and Y both have shape (2, 3), so X * Y is the element-wise product at every grid point.

! Common Errors (And How to Fix Them)

❌ Unpacking only one variable

meshgrid returns two arrays, so a single target throws away the structure:

grid = np.meshgrid(x, y)   # grid is a LIST of two arrays, not X

✅ Fix: unpack both outputs:

X, Y = np.meshgrid(x, y)

❌ Surface looks transposed or flipped

Mixing the 'xy' and 'ij' conventions swaps the axes of the result.

✅ Fix: pick one convention on purpose — 'xy' for plots, 'ij' for matrix-style math — and check the output shape.

❌ Running out of memory on a huge grid

Two full dense grids of a million-by-million points is enormous.

✅ Fix: pass sparse=True so the grids stay 1-by-N and N-by-1 and only broadcast when used.

🎯 Mini Challenge: Find the Closest Point to a Target

Build a grid, compute the distance from each grid point to a target, then report the smallest distance — all without loops.

import numpy as np

x = np.linspace(0, 4, 5)   # [0 1 2 3 4]
y = np.linspace(0, 4, 5)

X, Y = np.meshgrid(x, y)

# Target point
tx, ty = 2.0, 3.0

# Distance from every grid point to the target
dist = np.sqrt((X - tx)**2 + (Y - ty)**2)

print("Closest distance:", dist.min())   # Closest distance: 0.0
print("Number of grid points:", dist.size)   # Number of grid points: 25

# ✅ Expected output:
# Closest distance: 0.0
# Number of grid points: 25

❓ Frequently Asked Questions

Lesson complete — you can grid the plane!

You can turn two vectors into coordinate arrays with np.meshgrid, evaluate a function over an entire grid in one expression, choose between 'xy' and 'ij' indexing, and shrink memory with sparse=True.

Practice quiz

How many arrays does np.meshgrid(x, y) return?

  • Two 2D coordinate arrays
  • One flat array
  • Three arrays
  • A single scalar

Answer: Two 2D coordinate arrays. meshgrid returns two arrays, conventionally X and Y, for the grid coordinates.

What does the X output of np.meshgrid hold?

  • The y-coordinate of each point
  • The x-coordinate of each grid point
  • Random noise
  • The distances

Answer: The x-coordinate of each grid point. X holds the x-coordinate at every grid cell; Y holds the y-coordinate.

For x=[0,1,2] and y=[0,1], what shape do X and Y have (default 'xy')?

  • (3, 2)
  • (6,)
  • (2, 3)
  • (3, 3)

Answer: (2, 3). Default 'xy' gives shape (len(y), len(x)) = (2, 3).

Why does meshgrid let you evaluate f(x, y) with no loops?

  • It compiles a loop secretly
  • It only works on scalars
  • It caches values
  • X and Y are arrays, so element-wise expressions cover every cell at once

Answer: X and Y are arrays, so element-wise expressions cover every cell at once. Because X and Y are arrays, one expression like X**2 + Y**2 covers the whole grid.

What does the default indexing='xy' produce versus indexing='ij'?

  • 'xy' shape (len(y), len(x)); 'ij' shape (len(x), len(y))
  • Both give the same shape
  • 'xy' is 1D, 'ij' is 3D
  • 'xy' reverses the values

Answer: 'xy' shape (len(y), len(x)); 'ij' shape (len(x), len(y)). 'xy' is Cartesian (len(y), len(x)); 'ij' is matrix-style (len(x), len(y)).

What does sparse=True return instead of full dense grids?

  • Nothing
  • Broadcastable arrays of shape (1, len(x)) and (len(y), 1)
  • A flattened list
  • The original vectors unchanged

Answer: Broadcastable arrays of shape (1, len(x)) and (len(y), 1). sparse=True returns broadcastable 1xN and Nx1 arrays that save memory.

For x=[1,2,3], y=[10,20], X, Y = np.meshgrid(x, y); what is X * Y?

  • [[1 2 3] [10 20 30]]
  • [[11 22 33]]
  • [[10 20 30] [20 40 60]]
  • 60

Answer: [[10 20 30] [20 40 60]]. Element-wise product at each grid point gives [[10 20 30] [20 40 60]].

What goes wrong if you write grid = np.meshgrid(x, y) with one target?

  • grid becomes a list of two arrays, not X
  • It raises a SyntaxError
  • x and y are deleted
  • It returns a scalar

Answer: grid becomes a list of two arrays, not X. A single target captures the list of both arrays; unpack as X, Y instead.

Why does sparse=True save memory on large grids?

  • It deletes the data
  • It uses integers only
  • The coordinate values are never physically duplicated
  • It stores to disk

Answer: The coordinate values are never physically duplicated. Broadcastable grids avoid duplicating the values across the full grid.

Which dense slicing shortcut is the equivalent of meshgrid(indexing='ij')?

  • np.linspace
  • np.arange
  • np.mgrid
  • np.zeros

Answer: np.mgrid. np.mgrid is the dense, slicing-based equivalent of meshgrid with 'ij' indexing.

Continue this course

Frequently asked questions

What does np.meshgrid actually return?

np.meshgrid(x, y) returns two 2D arrays, conventionally called X and Y, that together give the coordinates of every point on a grid. X holds the x-coordinate of each grid point and Y holds the y-coordinate. Pairing X[i, j] with Y[i, j] gives one (x, y) point, so you can evaluate any function f(x, y) over the whole grid in a single vectorized expression.

What is the difference between indexing='xy' and indexing='ij'?

indexing='xy' (the default) is Cartesian: the first input varies along the columns and the output shape is (len(y), len(x)), which matches how plotting libraries expect data. indexing='ij' is matrix-style: the first input varies along the rows and the output shape is (len(x), len(y)). Use 'xy' for plots and 'ij' when you think in matrix row/column terms.

Why would I pass sparse=True to np.meshgrid?

sparse=True returns broadcastable arrays of shape (1, len(x)) and (len(y), 1) instead of full 2D grids. They still combine correctly thanks to broadcasting, but they use far less memory because the values are never physically duplicated. For large grids this can save a lot of RAM while giving identical results in any element-wise calculation.

When should I use np.mgrid or np.ogrid instead?

np.mgrid and np.ogrid are slicing-based shortcuts that build grids with a NumPy slice syntax, for example np.mgrid[0:3, 0:3]. np.mgrid is the dense equivalent of meshgrid(indexing='ij') and np.ogrid is the sparse, open-grid version. meshgrid is more explicit and flexible, so prefer it when readability matters; reach for mgrid/ogrid for quick index grids.