From plt.plot() to ax.plot(): Why the Object‑Oriented API Wins for Complex Matplotlib Projects
The pyplot state‑machine can break down in complex plots. Switching to Matplotlib’s Object‑Oriented API—using Figure and Axes objects—provides deterministic control over multiple figures and subplots. This blog explains why, with a clear example and actionable steps.
04 Jul 2026, 09:20 UTC

Problem: The Fragile State‑Machine
When you first start with Matplotlib you’ll often write something like:
import matplotlib.pyplot as plt
plt.plot([1,2,3],[4,5,6])
plt.title('Quick Plot')
plt.show()
That’s the pyplot interface – a global, state‑machine that keeps “current figure” and “current axes” in a hidden stack. It’s fast for a one‑liner, but the very same globals become a source of subtle bugs when you need several figures, subplots, or when you start threading.
Typical symptoms:
- Calling
plt.title()after creating a new figure sometimes changes the wrong plot. - Adding a second window with
plt.figure()can overwrite or delete the first if you’re not careful. - Mixing
plt.subplot()calls in a loop can leave you with a dozen “current axes” that you can’t reliably reference later.
Thesis: Switch to the Object‑Oriented API
The Object‑Oriented (OO) API gives you explicit Figure and Axes instances. You write fig, ax = plt.subplots() and then call ax.plot(). Every artist knows exactly which container it lives in, so the code is deterministic, thread‑safe, and easier to read.
State‑Machine in Action
Below is a minimal script that creates two figures using the state‑machine. Notice how the plt.title() call refers to whatever figure was last made.
import matplotlib.pyplot as plt
# Figure 1
plt.plot([1,2,3], [4,5,6])
plt.title('Figure 1')
# Figure 2 – the state machine switches here
plt.figure()
plt.plot([1,2,3], [6,5,4])
plt.title('Figure 2')
plt.show()
If you later add another plt.plot() without explicitly selecting the axes, you’ll be modifying the “current” axes, which might be Figure 2 or even a subplot inside it. The result is hard to track.
The Object‑Oriented Approach
Using the OO API you explicitly create the containers:
import matplotlib.pyplot as plt
# Create two separate figures
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
# Plot on each figure independently
ax1.plot([1,2,3], [4,5,6])
ax1.set_title('Figure 1')
ax2.plot([1,2,3], [6,5,4])
ax2.set_title('Figure 2')
plt.show()
Now ax1 and ax2 are guaranteed to refer to the correct axes, no matter the order of subsequent calls. This pattern scales to dozens of subplots, shared axes, or even separate windows opened in a GUI loop.
Worked Example: Multi‑Plot Layout
Suppose you need a 2×2 grid of subplots with shared X‑axis but independent Y‑axes. The state‑machine version is terse but can get confusing:
plt.subplot(2,2,1); plt.plot(x1, y1); plt.title('A')
plt.subplot(2,2,2); plt.plot(x2, y2); plt.title('B')
plt.subplot(2,2,3); plt.plot(x3, y3); plt.title('C')
plt.subplot(2,2,4); plt.plot(x4, y4); plt.title('D')
plt.show()
In the OO world you ask for the layout once, then address each Axes directly:
fig, axes = plt.subplots(2, 2, sharex=True)
axes[0, 0].plot(x1, y1)
axes[0, 0].set_title('A')
axes[0, 1].plot(x2, y2)
axes[0, 1].set_title('B')
axes[1, 0].plot(x3, y3)
axes[1, 0].set_title('C')
axes[1, 1].plot(x4, y4)
axes[1, 1].set_title('D')
plt.show()
Even though the OO code is a bit longer, the intent is crystal clear: you’re explicitly telling Matplotlib which axes to modify. If you later add a fourth subplot or change the shared‑axis configuration, you only touch the fig, axes = plt.subplots() line.
Trade‑Offs & Limitations
- Boilerplate: You must store references to
FigureandAxesobjects. For very small scripts the extra lines feel noisy. - Learning Curve: New users accustomed to
plt.plot()may find the OO syntax unfamiliar at first. - Large Datasets: If you’re plotting millions of points, the OO API can create a large number of
Artistobjects, which may slow down rendering. In that case considerLineCollectionorscatterwith a single artist. - Threading: Matplotlib is not fully thread‑safe. Even with the OO API you must ensure that all GUI updates happen on the main thread or use
matplotlib.use('Agg')for headless rendering.
Actionable Takeaway
When you start a project that will grow beyond a handful of plots, adopt the OO API from the beginning:
- Replace every
plt.plot()withax.plot()whereaxcomes fromplt.subplots()orFigure.add_subplot(). - Keep a mapping of figure IDs to
Figureobjects if you need to reference them later (e.g., for saving or updating). - Test with a simple two‑figure script: create each figure, set a title, and call
plt.show(). Verify that the titles appear on the correct windows. - For long‑running applications, use the
FigureManagerto keep track of open windows and avoid accidental closure. - Document the figure/axes layout in your code comments so that future contributors can see the intended structure.
By making the container objects explicit, you eliminate “current figure” bugs, simplify maintenance, and align your code with Matplotlib’s recommended best practices.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.