Matplotlib’s Constrained Layout: Automatic Subplot Spacing Made Simple
Constrained layout in Matplotlib automatically adjusts subplot spacing for titles, labels, and colorbars. This guide explains how it works, shows a working example, and highlights common pitfalls and performance notes.
05 Jul 2026, 03:02 UTC

Why Constrained Layout Matters
When you create multiple subplots in Matplotlib, the default margins are fixed. If you later change a title, tick label font size, or add a colorbar, the text can start to overlap the axes or each other. The traditional fig.tight_layout() call fixes the issue for a single draw, but it doesn’t adapt if you modify the plot after rendering or if you add new artists such as legends or colorbars. Constrained layout solves this by running a layout solver on every draw, automatically reserving space for all standard artists.
How Constrained Layout Works
When constrained_layout=True is set on a Figure or passed to plt.subplots, Matplotlib builds a 2‑D grid that represents the figure’s pixel space. Each subplot is a cell in that grid. The solver inspects every artist that can affect the figure’s bounding box—titles, axis labels, tick labels, colorbars, and even legends positioned with bbox_to_anchor. It then shrinks or expands the grid cells so that all these artists fit without overlap. Because the solver runs on every draw, any change to the plot’s content automatically triggers a new layout pass.
Worked Example
Below is a complete script that demonstrates a 2×2 grid of subplots, each with a different image, and a shared colorbar on the right side. Notice how the colorbar is added after the subplots are created; the layout engine still reserves space for it.
# Run this in a Python environment with matplotlib 3.4+.
import matplotlib.pyplot as plt
import numpy as np
# Create a 2x2 grid with constrained layout enabled.
fig, axs = plt.subplots(2, 2,
figsize=(6, 4),
constrained_layout=True)
# Generate some example data.
for i, ax in enumerate(axs.flat, 1):
data = np.random.rand(10, 10) * i
im = ax.imshow(data, cmap='viridis')
ax.set_title(f"Plot {i}")
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
# Add a single shared colorbar on the right side of the grid.
# Note: the solver will automatically shrink the right column to make room.
cbar = fig.colorbar(im, ax=axs[:, 1])
cbar.set_label("Intensity")
# Save the figure.
fig.savefig("constrained_layout_demo.png")
Running the script produces a figure where none of the titles, labels, or the colorbar overlap the axes. If you comment out constrained_layout=True, the colorbar will cover the right‑most column, and the titles may be clipped.
Limitations & Common Pitfalls
- Manual Axes Placement: Using
fig.add_axesorplt.axesto position axes manually bypasses the solver. Axes added this way will not be adjusted, so they can collide with automatically spaced subplots. If you need manual placement, avoidconstrained_layouton that figure. - Mixing with Tight Layout: Applying both
constrained_layout=Trueandfig.tight_layout()causes conflicting adjustments and can produce unpredictable spacing. Pick one method per figure. - External Legends: Legends positioned with
bbox_to_anchoroutside the axes may still be clipped when saving. Usesavefig(..., bbox_inches='tight')in addition to constrained layout to ensure everything is captured. - Version Sensitivity: Constrained layout was introduced in Matplotlib 3.4. In earlier versions it may behave differently or be unavailable. Always verify with
matplotlib.__version__.
Practical Verification Tips
- Check the Matplotlib version:
import matplotlib; print(matplotlib.__version__). Constrained layout requires 3.4 or newer. - Render the example above and visually inspect the PNG. Confirm that titles, tick labels, and the colorbar are all visible and not overlapping.
- Toggle
constrained_layout=Falseand rerun. Notice the colorbar now overlaps the right‑most subplot. - When adding an external legend, save the figure both with and without
bbox_inches='tight'and compare the outputs.
Performance Considerations
The layout solver adds a small per‑draw cost. For static figures or interactive plots that are redrawn infrequently, this overhead is negligible. However, in tight animation loops where performance is critical, consider using fixed margins or disabling constrained layout to avoid the solver’s extra calculations.
Nested Panels & Subfigures
Constrained layout also works with GridSpec and Figure.subfigures. You can nest panels, and the solver will calculate spacing for the entire hierarchy. This is especially useful when creating complex dashboards or publication‑quality figures with multiple panels that share axes or colorbars.
Global Configuration
If you prefer constrained layout for every figure by default, set the rcParams entry once in your script or configuration file:
import matplotlib as mpl
mpl.rcParams['figure.constrained_layout.use'] = True
After this, every new figure will automatically enable the solver unless you explicitly override it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.