Managing Multiple Figures in Matplotlib: OO API vs. Pyplot State Machine
Learn how the Object‑Oriented Matplotlib API lets you create independent figures, avoid state‑collision bugs, and manage multi‑panel layouts safely.
09 Aug 2026, 20:26 UTC

Two Figures, One Goal: Avoiding State‑Collision in Matplotlib
When you run a quick script that calls plt.plot([1,2,3]) and later add another chart, the second call may unintentionally draw on the same axes. The Pyplot interface keeps a single global drawing surface, so every plot command implicitly targets the current figure and axes. In a larger application or a script that creates multiple figures, that hidden state becomes a source of bugs.
The Core Thesis
The Object‑Oriented (OO) API lets you create explicit Figure and Axes instances, so each plot call targets the object you intend. This makes it safe to manage several independent visualizations in the same process.
Why the Global State Is a Problem
The Pyplot “state‑machine” works like an invisible canvas that you never name. Functions such as plt.plot automatically operate on the current figure (plt.gcf()) and the current axes (plt.gca()). In a notebook this is convenient, but in a script that builds several figures the calls can silently share the same axes, leading to overlapping or missing data.
For example, two consecutive plt.plot calls without resetting the current axes will draw both lines on the same ax object, producing a single multi‑line plot instead of two separate charts. This can be verified by printing the current axes ID before and after the calls; identical IDs indicate the same axes was used.
Running a short test in a Python REPL:
import matplotlib.pyplot as plt
plt.plot([0,1,2], [0,1,0])
print('Before second plot, current axes id:', id(plt.gca()))
plt.plot([0,1,2], [0,-1,0])
print('After second plot, current axes id:', id(plt.gca()))
Both prints will show the same ID, confirming the state‑sharing behavior.
The OO Alternative
The OO API removes the hidden state. You create a Figure with plt.figure() or fig, ax = plt.subplots(). The returned ax is the only surface that receives ax.plot commands. Because you hold a reference to that ax, you cannot accidentally plot on another axes.
Key advantages:
- Explicit control over which axes receives the data.
- Easy to arrange multiple sub‑plots in a grid using
fig, axs = plt.subplots(2, 2). - Better compatibility with GUI toolkits that require you to pass an axes handle.
When using the OO style, you must keep the Figure and Axes objects alive; otherwise the plot will disappear when the variable goes out of scope.
Concrete Example: Two Independent Figures
Below is a self‑contained script that creates two separate figures and verifies that each plot targets the correct axes.
import matplotlib.pyplot as plt
# First figure
fig1, ax1 = plt.subplots()
ax1.plot([0, 1, 2], [0, 1, 0], label='sin')
ax1.set_title('Figure 1')
ax1.legend()
# Second figure
fig2, ax2 = plt.subplots()
ax2.plot([0, 1, 2], [0, -1, 0], color='red', label='cos')
ax2.set_title('Figure 2')
ax2.legend()
# Verification: print object identities
print('Figure 1 id:', id(fig1))
print('Figure 2 id:', id(fig2))
print('Axes 1 id:', id(ax1))
print('Axes 2 id:', id(ax2))
# Optional: save each figure to demonstrate independence
fig1.savefig('fig1.png')
fig2.savefig('fig2.png')
Run this script in any environment where Matplotlib ≥ 3.5 is installed. The printed IDs should all be distinct, showing that ax1.plot and ax2.plot acted on separate objects. If you instead used the Pyplot style — calling plt.plot twice without an intervening plt.figure() — the two calls would share the same axes and the titles would overwrite each other.
Trade‑off and Limitation
The OO approach requires you to manage Figure and Axes references, which can feel verbose for quick experiments. Users accustomed to MATLAB‑style scripting may need a short adjustment period. A common pitfall is mixing plt.plot (state‑machine) calls with ax.plot in the same file; the later call may silently affect the wrong axes, leading to subtle visual errors. Additionally, creating many figures without closing them can consume memory, so it is good practice to call fig.clf() or fig.savefig() and then plt.close(fig) when a figure is no longer needed.
Actionable Closing
If you are building a dashboard, a multi‑panel report, or any code that may evolve, start by using fig, ax = plt.subplots() to create each panel explicitly. Verify your work by printing the object IDs or by checking that each axis shows its own title and legend. For a 2×2 grid, the same subplots call returns a NumPy‑style array of axes, letting you index them directly:
fig, axs = plt.subplots(2, 2, figsize=(8, 6))
for i, ax in enumerate(axs.flat):
ax.plot([0, 1, 2], [i, i+1, i+2])
ax.set_title(f'Panel {i+1}')
plt.tight_layout()
plt.show()
This pattern guarantees that each subplot is independent, prevents label clipping via tight_layout, and keeps the code readable. Remember: the global Pyplot state is a convenience for exploratory work, not a robust foundation for production‑grade figure management.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.