matplotlib
data visualization
subplot adjustment
plot spacing
Python plotting

Improve subplot size/spacing with many subplots

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

When creating figures with many subplots in matplotlib, the default spacing often causes overlapping titles, labels, and tick marks. Matplotlib provides several tools to control subplot size and spacing: plt.tight_layout() for automatic adjustment, plt.subplots_adjust() for manual control, GridSpec for advanced layouts, and constrained_layout for a modern automatic approach.

The Problem: Default Spacing

python
1import matplotlib.pyplot as plt
2import numpy as np
3
4fig, axes = plt.subplots(3, 3, figsize=(8, 8))
5for ax in axes.flat:
6    ax.plot(np.random.randn(50))
7    ax.set_title('Title')
8    ax.set_xlabel('X Label')
9    ax.set_ylabel('Y Label')
10plt.show()
11# Labels and titles overlap — hard to read

Method 1: tight_layout() (Simplest)

plt.tight_layout() automatically adjusts subplot spacing to prevent overlap:

python
1fig, axes = plt.subplots(3, 3, figsize=(10, 8))
2for ax in axes.flat:
3    ax.plot(np.random.randn(50))
4    ax.set_title('Title')
5    ax.set_xlabel('X Label')
6    ax.set_ylabel('Y Label')
7
8plt.tight_layout()
9plt.savefig('subplots.png', dpi=150)
10plt.show()

Add padding between subplots:

python
1plt.tight_layout(pad=2.0)          # Padding around the whole figure
2plt.tight_layout(w_pad=1.0)        # Horizontal padding between subplots
3plt.tight_layout(h_pad=1.5)        # Vertical padding between subplots
4plt.tight_layout(pad=1.0, w_pad=0.5, h_pad=1.0)  # All three

Method 2: subplots_adjust() (Manual Control)

For precise control over all spacing parameters:

python
1fig, axes = plt.subplots(3, 3, figsize=(10, 8))
2for ax in axes.flat:
3    ax.plot(np.random.randn(50))
4    ax.set_title('Title')
5
6fig.subplots_adjust(
7    left=0.1,      # Left edge of subplots (fraction of figure width)
8    right=0.95,    # Right edge
9    top=0.92,      # Top edge
10    bottom=0.08,   # Bottom edge
11    wspace=0.3,    # Width space between subplots
12    hspace=0.4     # Height space between subplots
13)
14plt.show()
ParameterWhat it controlsDefault
leftLeft margin0.125
rightRight margin0.9
topTop margin0.88
bottomBottom margin0.11
wspaceHorizontal gap between subplots0.2
hspaceVertical gap between subplots0.2

Method 3: constrained_layout (Modern)

constrained_layout is a newer alternative to tight_layout that handles more edge cases, including colorbars and legends:

python
1fig, axes = plt.subplots(3, 3, figsize=(10, 8), constrained_layout=True)
2for ax in axes.flat:
3    im = ax.imshow(np.random.rand(10, 10))
4    ax.set_title('Heatmap')
5    fig.colorbar(im, ax=ax, shrink=0.6)
6
7plt.show()
8# Colorbars are properly spaced — tight_layout often fails here

Enable it globally:

python
plt.rcParams['figure.constrained_layout.use'] = True

Method 4: GridSpec (Advanced Layouts)

GridSpec gives full control over subplot sizes and positions:

python
1from matplotlib.gridspec import GridSpec
2
3fig = plt.figure(figsize=(12, 8))
4
5gs = GridSpec(3, 3, figure=fig, wspace=0.4, hspace=0.5)
6
7# Regular grid
8for i in range(3):
9    for j in range(3):
10        ax = fig.add_subplot(gs[i, j])
11        ax.plot(np.random.randn(20))
12        ax.set_title(f'({i},{j})')
13
14plt.show()

Subplots of Different Sizes

python
1fig = plt.figure(figsize=(12, 8))
2gs = GridSpec(3, 3, figure=fig, wspace=0.3, hspace=0.3)
3
4# Large plot spanning 2 rows and 2 columns
5ax_main = fig.add_subplot(gs[0:2, 0:2])
6ax_main.set_title('Main Plot')
7
8# Smaller plots on the right
9ax_right1 = fig.add_subplot(gs[0, 2])
10ax_right2 = fig.add_subplot(gs[1, 2])
11
12# Bottom row
13ax_bottom = fig.add_subplot(gs[2, :])
14ax_bottom.set_title('Full Width Bottom')
15
16plt.tight_layout()
17plt.show()

Method 5: Increase Figure Size

Sometimes the simplest fix is making the figure larger:

python
1# Default size — too cramped for 4x4 subplots
2fig, axes = plt.subplots(4, 4, figsize=(8, 6))
3
4# Better — give each subplot more room
5fig, axes = plt.subplots(4, 4, figsize=(16, 12))
6plt.tight_layout()
7plt.show()

A good rule of thumb: allocate at least 3-4 inches per subplot in each dimension.

Removing Redundant Labels

When subplots share axes, remove inner labels to save space:

python
1fig, axes = plt.subplots(3, 3, figsize=(10, 8), sharex=True, sharey=True)
2for ax in axes.flat:
3    ax.plot(np.random.randn(50))
4
5# Remove inner tick labels
6for ax in axes.flat:
7    ax.label_outer()  # Only keeps labels on outer edges
8
9plt.tight_layout()
10plt.show()

Suptitle with Subplots

fig.suptitle() often overlaps the top row. Fix with top parameter:

python
1fig, axes = plt.subplots(2, 2, figsize=(10, 8))
2fig.suptitle('My Figure Title', fontsize=16)
3
4for ax in axes.flat:
5    ax.plot(np.random.randn(30))
6    ax.set_title('Subplot Title')
7
8plt.tight_layout(rect=[0, 0, 1, 0.95])  # Leave room for suptitle
9plt.show()

Common Pitfalls

  • tight_layout() after all content is added: Call plt.tight_layout() after all subplots, titles, labels, and legends are set. Calling it too early means it calculates spacing without accounting for later additions.
  • constrained_layout + tight_layout conflict: Do not use both. They are mutually exclusive approaches. If constrained_layout=True is set, do not call tight_layout().
  • Colorbars with tight_layout: tight_layout() does not account for colorbars well. Use constrained_layout=True instead, or manually adjust with subplots_adjust().
  • DPI and saved figure size: The spacing that looks good on screen may look different when saved at high DPI. Always check the saved output with plt.savefig('plot.png', dpi=150, bbox_inches='tight').
  • Too many subplots: Beyond ~25 subplots (5x5), individual plots become too small to read regardless of spacing. Consider paginating across multiple figures or using a different visualization approach.

Summary

  • Use plt.tight_layout() as the first fix for overlapping labels — it handles most cases
  • Use constrained_layout=True for figures with colorbars, legends, or complex elements
  • Use fig.subplots_adjust(wspace=, hspace=) for manual fine-tuning of spacing
  • Use GridSpec for non-uniform subplot sizes and advanced layouts
  • Use sharex=True, sharey=True with label_outer() to eliminate redundant tick labels
  • Increase figsize to give each subplot more room — sometimes the simplest solution

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.