A stdout Progress Bar Without a Library (Python)

tqdm is wonderful but adds 30k of dependencies for a 30-line job. Here is the pure-stdlib progress bar I drop into ETL scripts when I just want to know how far through the file I am.

Python
Compiler
3 snippets
input-output
code-template
py-generators
rajtanaka

By @rajtanaka

March 31, 2026

·

Updated May 20, 2026

359 views

8

Rate

from __future__ import annotations
import sys
import time

# A bar that fits in 30 lines and never lies. Width 40 columns; ETA computed
# from the average rate over the whole run so a slow start does not make the
# end-of-run ETA wildly optimistic.

def bar(total, width=40):
    start = time.perf_counter()
    last_render = 0.0
    isatty = sys.stdout.isatty()

    def render(i):
        nonlocal last_render
        now = time.perf_counter()
        if isatty and (now - last_render) < 0.05 and i != total:
            return
        last_render = now
        frac = i / total if total > 0 else 1.0
        filled = int(frac * width)
        elapsed = now - start
        rate = i / elapsed if elapsed > 0 else 0
        eta = (total - i) / rate if rate > 0 else 0
        line = f'[{"#" * filled}{"." * (width - filled)}] {i}/{total}  rate={rate:5.0f}/s  eta={eta:5.1f}s'
        if isatty:
            print('\r' + line, end='', flush=True)
        else:
            print(line, flush=True)
        if i == total and isatty:
            print()

    return render

N = 50
render = bar(N)
for i in range(1, N + 1):
    time.sleep(0.005)  # pretend work
    render(i)

Three small choices keep this honest. The redraw throttle (0.05s) means the bar updates at 20fps regardless of how fast the loop is, so a hot loop does not pay a syscall on every iteration. The TTY check makes the bar collapse into a flat print when output is piped to a file, so log files stay readable. The rate calculation uses elapsed-since-start rather than a windowed estimate, which is slower to react but never produces a 'eta=0.1s' lie when the loop has been running for an hour. I have shipped this in two ETL scripts; the first time anyone needs more (multi-bar, nested progress, byte-rate units) I switch to tqdm and stop pretending.