Plotting in a non-blocking way with Matplotlib
ML System Design practice on Codemia
Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.
To plot with Matplotlib without blocking your script, enable interactive mode with plt.ion() and use plt.pause() to process GUI events. This lets the plot window stay open and update while your code continues executing. For a one-shot non-blocking display, plt.show(block=False) also works, but you still need plt.pause() to keep the window responsive.
The Blocking Problem
By default, plt.show() blocks program execution until you close the plot window. This is fine for one-off analysis, but it stops your script dead when you need to update a plot in real time or continue processing data while the plot is visible.
Method 1: Interactive Mode with plt.ion()
Interactive mode is the recommended approach for real-time plotting. It tells Matplotlib to draw without blocking and to accept updates to existing figures:
The key calls:
plt.ion()enables interactive modefig.canvas.draw_idle()schedules a redrawplt.pause(0.05)processes GUI events and gives the backend time to repaintplt.ioff()andplt.show()at the end keep the final plot visible
Method 2: show(block=False)
For simpler cases where you just want the plot to appear without halting the script:
Without the plt.pause() at the end, the window may flash and close immediately when the script finishes.
Updating Plot Data Efficiently
A common mistake is calling ax.plot() repeatedly inside a loop. This creates a new Line2D object every iteration, accumulating objects and slowing down the figure. Instead, create the artist once and update its data:
Performance Comparison
| Approach | Memory | Speed | Recommended |
ax.plot() in every iteration | Grows linearly | Degrades over time | No |
line.set_data() to update | Constant | Consistent | Yes |
ax.clear() then ax.plot() | Constant | Slower (full redraw) | Only if axes change |
canvas.blit() (blitting) | Constant | Fastest | For high frame rates |
Blitting for High Performance
When you need higher frame rates (monitoring dashboards, animations), blitting redraws only the changed parts of the canvas:
Blitting avoids redrawing the axes, labels, and background on every frame. On supported backends, this can be 5 to 10 times faster than full redraws.
Using FuncAnimation for Structured Animation
For production animation code, FuncAnimation provides a cleaner structure than manual loops:
FuncAnimation handles the event loop, timing, and blitting automatically. You can also save the animation to a file:
Backend Configuration
Non-blocking plotting requires an interactive GUI backend. Common backends:
| Backend | Platform | Set With |
TkAgg | All platforms (default) | matplotlib.use("TkAgg") |
Qt5Agg | All platforms (needs PyQt5) | matplotlib.use("Qt5Agg") |
macosx | macOS native | matplotlib.use("macosx") |
Agg | Headless (no GUI) | matplotlib.use("Agg") |
inline | Jupyter notebooks | %matplotlib inline |
widget | Jupyter (interactive) | %matplotlib widget |
The Agg backend is non-interactive and cannot display windows. If your script silently does nothing when you call plt.show(), check your backend:
Jupyter Notebooks
Non-blocking behavior in Jupyter differs from desktop scripts:
With %matplotlib widget, you get a live, interactive plot directly in the notebook cell that you can update programmatically.
Threading Considerations
Matplotlib is not thread-safe. If you update plots from a background thread while the main thread runs the GUI event loop, you will get crashes or corrupted renders. Keep all Matplotlib calls on the main thread:
Common Pitfalls
Forgetting plt.pause() after show(block=False). Without pause, the GUI event loop never processes events, and the window appears frozen or closes immediately when the script exits.
Calling ax.plot() inside a loop instead of updating artists. Each plot() call creates a new Line2D object. After thousands of iterations, the figure becomes unresponsive. Use line.set_data() or line.set_ydata() instead.
Running on a headless server with no display. Non-blocking interactive plots require a display server (X11, Wayland, or macOS Aqua). On servers without a display, use the Agg backend and save to files instead: fig.savefig("plot.png").
Setting the backend after importing pyplot. The matplotlib.use() call must come before import matplotlib.pyplot. Once pyplot is imported, the backend is locked.
Mixing plt.ion() with FuncAnimation. FuncAnimation manages its own event loop via plt.show(). Enabling interactive mode can interfere with the animation timer. Use one approach or the other, not both.
Not keeping a reference to the animation object. If the FuncAnimation object gets garbage collected, the animation stops. Always assign it to a variable that stays in scope: ani = animation.FuncAnimation(...).
Summary
Use plt.ion() with plt.pause() for the simplest non-blocking Matplotlib workflow. Update existing line objects with set_data() instead of calling plot() repeatedly. For high-performance updates, use blitting or FuncAnimation. Verify your backend is interactive (not Agg). Keep all Matplotlib calls on the main thread when using multithreaded applications. In Jupyter, use %matplotlib widget with the ipympl package for interactive in-notebook plots.
Related reading

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack 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.