Matplotlib tight_layout wspace and hspace: What Actually Works

A sales manager sent me a 2×2 Matplotlib dashboard last month with a short note: “I set wspace and hspace, called tight_layout, and my spacing vanished.” She wasn’t doing anything odd. That’s simply how the two features interact.

tight_layout() has no wspace or hspace arguments. It calculates those two values itself and writes them over whatever you set earlier, so the obvious fix of passing them in just raises a TypeError.

Quick answer: make the gaps bigger with w_pad and h_pad, or call subplots_adjust() after tight_layout() when you need exact values:

fig.tight_layout(w_pad=3, h_pad=2)              # automatic, roomier
fig.subplots_adjust(wspace=0.45, hspace=0.6)    # exact, set last

In this tutorial, I’ll show what Matplotlib tight_layout wspace hspace settings actually do on one dashboard, measure the values tight_layout writes, and fix the three errors people hit along the way.

The sample dashboard with crowded subplots

Every example uses the same data: monthly H1 2026 sales in USD for four US regions, one subplot each. I ran everything on Python 3.12.5 and Matplotlib 3.11.2.

import matplotlib.pyplot as plt

months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
sales = {   # USD, H1 2026
    "Northeast": [182400, 175900, 191250, 204800, 210300, 198750],
    "South": [156300, 161850, 170400, 168900, 181200, 189600],
    "Midwest": [121700, 118400, 127950, 133600, 139250, 142800],
    "West": [201500, 196300, 214700, 221900, 228400, 235100],
}

fig, axes = plt.subplots(2, 2, figsize=(8, 6))
for ax, (region, values) in zip(axes.flat, sales.items()):
    ax.plot(months, values, marker="o", color="#1f6fb2")
    ax.set_title(f"{region} sales, H1 2026")
    ax.set_xlabel("Month")
    ax.set_ylabel("Sales (USD)")

plt.show()
Matplotlib 2x2 subplots of H1 2026 regional sales where titles, axis labels and tick labels overlap at the default spacing
At the default spacing, the bottom titles run into the Month labels and the right column’s y-axis labels hit the left plots.

The default subplot parameters don’t know your labels exist. If you want every spacing option in one place, my guide to setting the spacing between subplots in Matplotlib covers them all.

What does tight_layout() do to wspace and hspace?

wspace is the gap between columns and hspace the gap between rows, both as a fraction of the average subplot width or height. Here’s the same dashboard, printing them at each step:

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

# months and sales from the sample

fig, axes = plt.subplots(2, 2, figsize=(8, 6))
# same plotting loop as the sample

def show(step):
    p = fig.subplotpars
    print(f"{step:<31} wspace={p.wspace:.3f}  hspace={p.hspace:.3f}")

show("default")
fig.subplots_adjust(wspace=0.6, hspace=0.6)
show("subplots_adjust(0.6, 0.6)")
fig.tight_layout()
show("then tight_layout()")
fig.tight_layout(w_pad=4, h_pad=4)
show("tight_layout(w_pad=4, h_pad=4)")
fig.subplots_adjust(wspace=0.6)
show("subplots_adjust after it")

Output:

default                         wspace=0.200  hspace=0.200
subplots_adjust(0.6, 0.6)       wspace=0.600  hspace=0.600
then tight_layout()             wspace=0.336  hspace=0.378
tight_layout(w_pad=4, h_pad=4)  wspace=0.509  hspace=0.630
subplots_adjust after it        wspace=0.600  hspace=0.630
Command Prompt output showing Matplotlib subplotpars wspace and hspace before and after subplots_adjust and tight_layout
The 0.6 values from subplots_adjust() are replaced as soon as tight_layout() runs; only a later subplots_adjust() sticks.

How does this code work?

fig.subplotpars holds the live layout values. subplots_adjust(wspace=0.6, hspace=0.6) sets them, then tight_layout() measures the labels and writes 0.336 and 0.378 instead, because that’s all this chart needs.

With w_pad=4, h_pad=4 it wrote 0.509 and 0.630. The general guide to Matplotlib tight_layout() explains pad and rect too.

Add more space between subplots with w_pad and h_pad

These are tight_layout’s own spacing knobs. w_pad controls the gap between columns and h_pad the gap between rows, measured in multiples of the font size, not as fractions. Same dashboard, with roomier gaps:

import matplotlib.pyplot as plt

# months and sales from the sample
fig, axes = plt.subplots(2, 2, figsize=(8, 6))
# same plotting loop as the sample

fig.tight_layout(w_pad=3, h_pad=2)   # gaps in multiples of the font size
plt.show()
Matplotlib 2x2 sales subplots spaced with tight_layout w_pad=3 and h_pad=2 so no titles or labels overlap
With w_pad=3 and h_pad=2, every title, label and tick sits clear of its neighbor.

How does this code work?

tight_layout first finds the space your labels need, then adds the padding on top. Because it’s based on font size, the spacing still fits when you make the fonts bigger.

I use this for nearly every grid I build. It also works on single charts with long labels, like the ones in fixing Matplotlib x-axis label spacing.

Set exact wspace and hspace after tight_layout()

Sometimes a report template needs fixed gaps. Order matters here: let tight_layout fix the outer margins, then call subplots_adjust() with only the values you want to pin.

import matplotlib.pyplot as plt

# months and sales from the sample
fig, axes = plt.subplots(2, 2, figsize=(8, 6))
# same plotting loop as the sample

fig.tight_layout()                   # fixes the outer margins first
fig.subplots_adjust(wspace=0.45, hspace=0.6)   # then exact gaps
plt.show()
Matplotlib 2x2 sales subplots where subplots_adjust wspace 0.45 and hspace 0.6 is applied after tight_layout
tight_layout set the outer margins, and subplots_adjust() then fixed the gaps at 0.45 and 0.6.

How does this code work?

subplots_adjust() only changes the arguments you pass, so the left, right, top and bottom margins from tight_layout stay. The catch: change a label later and nothing re-checks it.

If your call seems to do nothing, read why subplots_adjust is not working. For outer margins only, see adjusting the bottom and right margins.

Use wspace and hspace with constrained layout instead

Constrained layout is the newer layout engine, and it’s the one place where wspace and hspace work together with automatic spacing. You set them on the engine:

import matplotlib.pyplot as plt

# months and sales from the sample
fig, axes = plt.subplots(2, 2, figsize=(8, 6), layout="constrained")
# same plotting loop as the sample

engine = fig.get_layout_engine()
print(engine.get())
engine.set(wspace=0.08, hspace=0.08)
plt.show()

Output:

{'h_pad': 0.04167, 'w_pad': 0.04167, 'hspace': 0.02, 'wspace': 0.02, 'rect': (0, 0, 1, 1)}
Matplotlib 2x2 sales subplots using layout constrained with layout engine wspace and hspace set to 0.08
Constrained layout keeps the labels clear and adds the extra wspace and hspace on top.

How does this code work?

engine.get() printed the defaults: wspace and hspace of 0.02 (a share of the figure), plus w_pad and h_pad of 0.04167 inches. Unlike tight_layout, it recalculates on every draw.

Needs Matplotlib 3.6+ for layout="constrained". The trade-offs are covered in Matplotlib constrained layout vs tight layout.

Common errors with tight_layout, wspace and hspace

These three came straight from my runs. The script prints each message instead of crashing:

import warnings
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

warnings.simplefilter("always")
warnings.showwarning = lambda m, *a, **k: print("UserWarning:", m)

fig, axes = plt.subplots(2, 2)
try:
    fig.tight_layout(wspace=0.3)
except TypeError as err:
    print("TypeError:", err)

print("1) gridspec_kw spacing, then tight_layout():")
fig, axes = plt.subplots(2, 2, gridspec_kw={"wspace": 0.6})
fig.tight_layout()
print("   gridspec wspace is still", axes[0, 0].get_gridspec().wspace)

print("2) constrained layout, then subplots_adjust():")
fig, axes = plt.subplots(2, 2, layout="constrained")
fig.subplots_adjust(wspace=0.5)

Output:

TypeError: Figure.tight_layout() got an unexpected keyword argument 'wspace'
1) gridspec_kw spacing, then tight_layout():
UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
   gridspec wspace is still 0.6
2) constrained layout, then subplots_adjust():
UserWarning: This figure was using a layout engine that is incompatible with subplots_adjust and/or tight_layout; not calling subplots_adjust.
Command Prompt output of the tight_layout wspace TypeError and the gridspec_kw and constrained layout UserWarnings
Passing wspace raises a TypeError, and the other two mixes print a UserWarning instead of changing the spacing.

TypeError: unexpected keyword argument ‘wspace’. tight_layout only accepts pad, w_pad, h_pad and rect. Swap in w_pad and h_pad.

gridspec_kw spacing turns tight_layout off. With gridspec_kw={"wspace": 0.6}, tight_layout skipped the figure entirely; the column gap stayed exactly the same. Drop the gridspec value or skip tight_layout.

subplots_adjust is ignored under constrained layout. Use engine.set() instead. Colorbars bring their own issues, covered in tight_layout with colorbars and GridSpec.

Which spacing option should you pick?

You wantUse
Automatic, a bit roomiertight_layout(w_pad=3, h_pad=2)
Exact gaps for a templatetight_layout(), then subplots_adjust(wspace=, hspace=)
Automatic plus wspace/hspacelayout="constrained" + engine.set()
No overlap whe