Python progress bars

tqdm not showing? Fixes for every common symptom

When tqdm (4.70.1) isn't showing up or prints a new line on every update, it's almost always a stream mismatch (tqdm writes to stderr, not stdout) or a console that doesn't handle carriage returns at the default 0.1-second refresh interval. Setting mininterval=30 (or TQDM_MININTERVAL=30, available since tqdm 4.66.0) stops log spam, and keeping len() on your iterable brings back the percentage.

6 min readtqdm and progress bars

At a glance

  • tqdm writes to stderr, so > out.log captures your prints but not the bar; add 2>&1.
  • In a log file every refresh is saved as text: use TQDM_MININTERVAL=30 or disable=None.
  • Use tqdm.write() instead of print() inside the loop.
  • No percentage means no len(): use enumerate(tqdm(x)), zip(tqdm(a), b) or total=.

Find your symptom

Start with the row that matches what you see:

SymptomCauseFix
Bar missing from out.logtqdm writes to stderr and > only redirects stdoutAdd 2>&1, or pass file=sys.stdout
Log file fills with copies of the barEvery refresh is saved as textTQDM_MININTERVAL=30, mininterval=30 or disable=None
New line on every updateprint() inside the looptqdm.write()
New lines in PyCharm's Run windowThe standard output console is not a full terminalTurn on "Emulate terminal in output console"
Count but no percentage or ETAThe iterable has no len()total=len(x) or enumerate(tqdm(x))
Bar appears only after a few secondsdelay=N is setLower or remove delay
Nested bars pile upInner bars keep the default leave=Trueposition=n and leave=False
IProgress not found in Jupyteripywidgets is missing in the kernel%pip install ipywidgets, then restart the kernel
Broken block characters on WindowsThe console only partly supports Unicodeascii=True
UnicodeEncodeError: 'charmap' codectqdm 4.68.0 or 4.68.1Upgrade, or pin tqdm!=4.68.0,!=4.68.1

No bar in logs, nohup, Docker or CI

By default, tqdm writes to stderr. If you run python script.py > out.log, you're only redirecting stdout, so the progress bar stays on your terminal instead of going into the file. To send both streams to the same place, add 2>&1 to your command. nohup does this for you when you start it from a terminal: both streams go to nohup.out.

If you don't want bars in your log files at all, pass disable=None to tqdm(). That tells tqdm to check if the output is a real terminal (TTY) and turn the bar off when it isn't, while disable=True turns it off everywhere. You can also set the environment variable TQDM_DISABLE=1 to kill all bars without touching your code.

When you do keep bars in logs or CI, remember how tqdm redraws: each refresh writes a carriage return plus a fresh copy of the bar. A terminal redraws the line in place, but a log file keeps every copy on one long line that grows fast, and some CI log viewers show each copy as a new line. A few ways to calm things down:

  • Set mininterval=30 in Python or TQDM_MININTERVAL=30 in your environment so the bar updates every 30 seconds instead of the default 0.1 seconds. In our test, a 2-second loop wrote 22 refreshes by default and 2 with TQDM_MININTERVAL=30.
  • For cloud log consoles like CloudWatch or Kubernetes, tqdm's FAQ suggests export TQDM_POSITION=-1.
  • With Docker Compose, tqdm's FAQ notes that docker-compose up shows no intermediate output; use docker-compose run instead, and set tty: true in your configuration.
Bash
# bar and output in one file, one refresh every 30 seconds
TQDM_MININTERVAL=30 python train.py > train.log 2>&1

# no bars at all
TQDM_DISABLE=1 python train.py > train.log 2>&1

A new line on every update

A regular print() statement inside your loop writes a full line to stdout, which breaks the bar line that tqdm is trying to hold in place on stderr. Switch print() to tqdm.write(), which prints your text above the bar and leaves the progress line intact.

Python
from time import sleep
from tqdm import tqdm

for job in tqdm(range(5), desc="jobs"):
    sleep(0.2)
    tqdm.write(f"done with job {job}")

Two other things will cause a fresh line on every tick even without print():

  • Consoles with limited cursor support: nested bars need a console that can move the cursor up a line, and tqdm's FAQ points out that IDLE, ConEmu and PyCharm lack full support for this. In PyCharm, turn on Emulate terminal in output console in the run configuration.
  • Windows consoles and Unicode: on Windows, tqdm installs colorama to keep nested bars on their own lines, and consoles that only partly support Unicode often need ascii=True to avoid broken block characters. If you're pinned to tqdm 4.68.0 or 4.68.1, those versions accidentally changed the ascii default and could raise UnicodeEncodeError: 'charmap' codec can't encode characters; 4.68.2 reverted it.

Bar stuck at 0%, no percentage, or no ETA

If your bar shows an iteration count and speed without a percentage bar or an ETA, tqdm doesn't know the total length of what you're looping over. Generators don't have a len(), and wrapping a list in enumerate() or zip() before passing it to tqdm hides the length too.

If you write tqdm(enumerate(x)), tqdm sees an enumerate object with no length. Flip the order to enumerate(tqdm(x)) so tqdm wraps the sequence directly, or pass total=len(x) yourself. Do the same thing with zip: write zip(tqdm(a), b) instead of wrapping the whole zip call.

Python
from tqdm import tqdm

names = ["a", "b", "c"]
sizes = [10, 20, 30]

for i, name in enumerate(tqdm(names)):
    pass

for name, size in zip(tqdm(names), sizes):
    pass

def rows():
    yield from range(1_000)

for row in tqdm(rows(), total=1_000):
    pass

If the bar doesn't show up at all for the first part of a run and then suddenly appears, check if delay=N is set, since that hides the bar for the first N seconds (the default is 0). Redraw timing is also controlled by miniters, mininterval (default 0.1 seconds), and maxinterval (which defaults to 10 seconds).

Notebook bars that do not render

In Jupyter notebooks, tqdm.notebook needs ipywidgets installed to draw the widget bar. Without it, you'll hit an IProgress not found error and the bar won't render.

Either install ipywidgets in that kernel's environment, or import with from tqdm.auto import tqdm, which falls back to a standard text bar when the widget isn't there. The Jupyter guide covers JupyterLab and VS Code.

Nested and parallel bars that overwrite each other

By default, tqdm sets leave=True, which keeps finished bars on screen when their loop ends. When you have an outer loop and an inner loop running together, completed inner bars pile up or collide with the outer bar unless you pin their lines.

Give each bar its own line with position=n (starting at 0 for the outer bar, 1 for the inner bar), and pass leave=False on the inner bar so it clears itself when each inner loop finishes.

Python
from time import sleep
from tqdm import tqdm

for epoch in tqdm(range(3), desc="epochs", position=0):
    for batch in tqdm(range(50), desc="batches", position=1, leave=False):
        sleep(0.01)

Check progress when you cannot see the terminal

When no terminal is attached (nohup, a container, a redirected log), Runpeek lets you view your existing tqdm bar in any browser through the tqdm-live Python package. Swap from tqdm import tqdm for from tqdm_live import tqdm, or call tqdm_live.patch() once before any bar is created, and each bar prints a runpeek ▶ https://runpeek.app/b/<uuid> link.

That line goes to stdout, which Python buffers when it writes to a file, so set PYTHONUNBUFFERED=1 to see it in the log straight away. Links need no account and expire 30 minutes after the last update.

FAQ

Why does tqdm print to stderr?

tqdm writes to stderr by default so your script's actual output on stdout stays clean. If you redirect a script with python script.py > out.log, your data goes into out.log while the progress bar still shows up on your terminal; use 2>&1 if you want both sent to the same place.

How do I stop tqdm from filling my log file?

Each refresh writes a fresh copy of the bar, and a log file keeps all of them. Pass disable=None to turn the bar off when the output isn't a terminal, or set mininterval=30 in code or TQDM_MININTERVAL=30 in your environment to refresh every 30 seconds instead of every 0.1.

How do I disable tqdm without changing code?

Set the environment variable TQDM_DISABLE=1 before running your script. Environment variable overrides have been supported since tqdm 4.66.0, and TQDM_DISABLE=1 turns all bars off without touching your Python files.

Why does tqdm work in the terminal but not in PyCharm?

PyCharm's standard output console isn't a full terminal, and tqdm's FAQ lists PyCharm, IDLE and ConEmu as lacking full support for moving the cursor up a line. Turn on "Emulate terminal in output console" in the run configuration, which JetBrains recommends for scripts that redraw a line with carriage returns.

Why does my bar only appear when the loop ends?

If you're using docker-compose up, tqdm's FAQ notes that it hides intermediate output; switch to docker-compose run with tty: true. Inside Python, check whether delay=N is set on the bar, or whether a large miniters or mininterval value is holding back redraws until the loop finishes.

Check on long runs from your phone

Change one import and every tqdm bar prints a link you can open on any device. Get a Discord or Slack message when the run finishes, fails or stalls.

Get a live linkpip install tqdm-live

Sources