A progress bar shows how much of a long-running task is complete. In Python, you can build a simple terminal bar without libraries, or use a package such as tqdm for a cleaner default.
Tested on: Python 3.13.3; kernel 6.14.0-37-generic; tqdm 4.67.1.
Quick answer: Python progress bar in the terminal
Use tqdm for the easiest progress bar. Use print(..., end="\r", flush=True) or sys.stdout.write("\r...") when you want a bar without a library.
from tqdm import tqdm
for item in tqdm(range(100)):
_ = item * 2The loop shows a live terminal bar with count, percentage, and ETA. For a no-library version, see Progress bar without a library below.
Python progress bar quick reference
| Task | Use |
|---|---|
| Easiest progress bar | tqdm(range(total)) |
| Progress bar without library | print(..., end="\r", flush=True) |
| Manual terminal bar | sys.stdout.write("\r...") + sys.stdout.flush() |
| Show percentage only | print(f"{percent:.0f}%", end="\r") |
| Progress bar for file lines | tqdm(file, total=line_count) |
| Progress bar for downloads | tqdm(total=size, unit="B", unit_scale=True) |
| Progress bar for pandas | tqdm.pandas() |
| Async progress bar | tqdm.asyncio |
| Nested progress bars | tqdm inside tqdm |
| Unknown total | spinner or alive-progress |
| GUI progress bar | PySimpleGUI / Tkinter |
| Web progress bar | Django + Celery/WebSocket |
What is a Python progress bar?
A progress bar gives feedback during long-running work. It usually shows current progress, total progress, percentage, elapsed time, or estimated time remaining. It is useful for loops, file processing, downloads, scraping, data processing, and batch jobs.
Progress bar without a library
Use carriage return \r to return to the start of the same terminal line. Use end="\r" to avoid printing a new line each update. Use flush=True or sys.stdout.flush() so output appears immediately.
The print() documentation describes end and flush. The sys module exposes stdout for direct terminal writes.
Percentage-only bar with print()
A percentage-only bar is enough for many scripts.
total = 10
for i in range(total + 1):
percent = (i / total) * 100
print(f"{percent:.0f}%", end="\r", flush=True)
print()You can also show count and percentage together:
done = 0
total = 50
for _ in range(total):
done += 1
print(f"Progress: {done}/{total} ({done / total:.0%})", end="\r", flush=True)
print()Avoid ordinary print() calls inside the loop unless you intentionally want output on separate lines. End with a blank print() to move to a new line after the bar finishes.
Custom bar with sys.stdout
Use sys.stdout.write() for direct terminal updates when you want a visible bar, not just a percentage.
import sys
def show_bar(done, total, width=20):
filled = int(width * done / total)
bar = "#" * filled + "-" * (width - filled)
sys.stdout.write(f"\r[{bar}] {done}/{total} ({done / total:.0%})")
sys.stdout.flush()
for i in range(1, 11):
show_bar(i, 10)
print()Common problems without a library
- Output does not update because buffering is not flushed.
- The bar prints on many lines instead of one line.
- Other
print()calls break the bar layout. - The terminal window is too narrow for the bar text.
- IDE or notebook output may not handle
\rlike a real terminal. - Total work is unknown, so a percentage bar is misleading.
- Division by zero when
totalis0.
Progress bar with tqdm
tqdm is the easiest option for most terminal loops. Wrap any iterable with tqdm(). It shows count, percentage, speed, elapsed time, and estimated remaining time automatically.
Install it when needed:
pip install tqdmBasic usage:
from tqdm import tqdm
for item in tqdm(range(100)):
_ = itemThe tqdm documentation describes it as a fast progress meter you wrap around an iterable.
Basic loop or iterable
from tqdm import tqdm
items = ["alpha", "beta", "gamma"]
for name in tqdm(items, desc="Processing"):
_ = name.upper()Use desc for a label and unit for custom units such as files or rows.
File line processing
Wrap a file iterator when you process lines one at a time:
from tqdm import tqdm
lines = ["row1", "row2", "row3"]
for line in tqdm(lines, desc="Lines"):
_ = line.strip()For a real file, use for line in tqdm(open("data.txt")): or pass total=line_count when you know the line count ahead of time.
File download by chunk size
When the total byte size is known, pass it to tqdm and update by chunk size.
import urllib.request
from tqdm import tqdm
url = "https://example.com/file.bin"
with urllib.request.urlopen(url) as response:
size = int(response.headers.get("Content-Length", 0))
with tqdm(total=size, unit="B", unit_scale=True, desc="Download") as bar:
while chunk := response.read(8192):
bar.update(len(chunk))Not every server sends Content-Length. When size is unknown, use a count-only bar or a spinner instead of a percentage bar.
pandas progress_apply
For pandas workflows, register tqdm once and use progress_apply().
import pandas as pd
from tqdm import tqdm
tqdm.pandas(desc="Rows")
df = pd.DataFrame({"value": range(1000)})
df["double"] = df["value"].progress_apply(lambda x: x * 2)See pandas DataFrame for table basics. Run pandas examples locally; the site Run button does not execute them reliably.
asyncio with as_completed
Use tqdm.asyncio for async iterators and task progress. This fits asyncio.as_completed() workflows better than forcing a sync bar around async code.
import asyncio
from tqdm.asyncio import tqdm_asyncio
async def work(n):
await asyncio.sleep(0.01)
return n
async def main():
tasks = [work(i) for i in range(20)]
results = []
for coro in tqdm_asyncio.as_completed(tasks):
results.append(await coro)
print(len(results))
asyncio.run(main())For a deep async tutorial, a separate article on tqdm asyncio as_completed is a better fit than expanding this page.
Disable in CI or non-interactive runs
Hide the bar when stdout is not a terminal, such as in cron jobs or CI logs.
import sys
from tqdm import tqdm
for i in tqdm(range(20), disable=not sys.stdout.isatty()):
_ = iOther terminal progress bar libraries
| Library | Best for |
|---|---|
| tqdm | Simple terminal loops |
| progressbar2 | Custom terminal widgets |
| progress | Minimal terminal bars/spinners |
| alive-progress | Animated terminal progress |
| rich | Rich terminal UI with progress, tables, logs |
Install examples:
pip install progressbar2
pip install progress
pip install alive-progress
pip install richBrief notes:
- progressbar2 — widget-style terminal bars with timers and ETA.
- progress — lightweight bars and spinners.
- alive-progress — animated bars; good when total is unknown.
- rich — progress plus tables, logging, and styled terminal output.
rich progress example
from rich.progress import track
for value in track(range(100), description="Working"):
_ = valuealive-progress for unknown total
A percentage progress bar needs a known total. When total is unknown, use alive-progress or rich for indeterminate-style feedback.
from alive_progress import alive_bar
with alive_bar(total=None, title="Working") as bar:
for _ in range(50):
bar()Which approach should you use?
| Situation | Use |
|---|---|
| Simple script, no extra packages | No-library bar with print or sys.stdout |
| Most CLI or data-processing loops | tqdm |
| App already uses rich output | rich |
| Animated bar or unknown total | alive-progress or rich |
| Desktop GUI app | PySimpleGUI / Tkinter |
| Web app with long tasks | Django + Celery + browser updates |
- Use a no-library bar for simple scripts or restricted environments.
- Use tqdm for most terminal workflows.
- Use GUI or web patterns only for desktop or browser apps.
GUI and web progress bars
Terminal progress bars do not belong in browser or desktop UI code. Use platform-specific patterns instead.
PySimpleGUI
PySimpleGUI provides a ProgressBar element for desktop GUI apps, not terminal scripts. It is a two-color horizontal or vertical bar updated from your event loop.
Use it when you build a GUI window. For terminal loops, use tqdm or a no-library bar instead. A dedicated PySimpleGUI progress bar article is a better home for full GUI examples.
Django / Celery / WebSocket
Django progress bars usually need backend task progress plus frontend updates. Common patterns combine Celery (or similar) for background work with polling, Server-Sent Events, or WebSockets in the browser.
Split the problem into task state on the server and progress UI in the client. A separate article such as “Django progress bar with Celery” fits that search intent better than a full tutorial on this page.
Progress bar best practices
- Update once per item or batch, not on every tiny sub-step.
- Avoid normal
print()inside the progress loop; usetqdm.write()when you must log. - Do not show fake percentages when total is unknown.
- Use meaningful units: files, rows, bytes, tasks.
- Disable bars in logs, cron jobs, CI, or non-interactive output when they add noise.
- Measure wall-clock time with Python measure execution time before tuning worker counts.
Summary
Use tqdm for most Python progress bars. Use print with end="\r" and flush=True, or sys.stdout.write() with flush(), for a no-library terminal bar. Use progressbar2, alive-progress, progress, or rich when you need custom terminal output. Use separate GUI or web patterns for PySimpleGUI and Django apps.

