Long-running scripts should tell users that work is still happening. Without feedback, a file conversion, download, data cleanup, or automation task may look frozen even when it is working normally. The Python tqdm library adds a progress bar to loops with very little code.
This guide covers installation, basic iterable wrapping, labels and units, manual updates, unknown totals, file processing, nested bars, pandas integration, downloads, logging, and production-friendly options. The examples use terminal progress bars, but tqdm also supports notebook and GUI-oriented integrations.
Progress indicators are especially useful in Python automation scripts and command-line tools. The Python CLI with argparse guide later in this batch shows how to combine progress with user-supplied options.
What Is tqdm?
tqdm wraps an iterable and displays completed items, total items, elapsed time, processing speed, and an estimated remaining time when a total is known. Its name comes from an abbreviation associated with progress in Arabic and the Spanish phrase meaning “I love you,” but in Python code you simply import tqdm.
The official tqdm documentation provides API details, and the tqdm PyPI page provides installation and release information.
Install tqdm
Create or activate a virtual environment, then install the package with the interpreter that will run your script:
python -m pip install tqdmVerify the installation:
python -c "import tqdm; print(tqdm.__version__)"The pip installation guide explains why python -m pip helps avoid installing into the wrong environment.
Your First Progress Bar
Wrap an iterable with tqdm():
import time
from tqdm import tqdm
for item in tqdm(range(100)):
time.sleep(0.03)The sleep call only simulates work. In a real program, the loop might resize an image, parse a file, call an API, or process one database record.
Add a Description
for item in tqdm(range(50), desc="Processing records"):
time.sleep(0.04)A short description tells the user what the bar represents. Keep it stable; changing a long label every iteration can make terminal output difficult to read.
Show Meaningful Units
The unit parameter replaces the generic iteration label:
files = ["a.csv", "b.csv", "c.csv"]
for filename in tqdm(files, desc="Converting", unit="file"):
time.sleep(0.4)For bytes, add unit_scale=True and often unit_divisor=1024:
total_bytes = 10_000_000
with tqdm(
total=total_bytes,
unit="B",
unit_scale=True,
unit_divisor=1024,
desc="Writing",
) as progress:
for chunk_size in [2_000_000] * 5:
time.sleep(0.2)
progress.update(chunk_size)Use tqdm with Lists, Generators, and Files
Any iterable can be wrapped. A list normally provides its length automatically:
names = ["Ava", "Noah", "Mia", "Leo"]
for name in tqdm(names, desc="Greeting"):
print(name)A generator may not expose a total. Pass one when it is known:
def squares(limit):
for number in range(limit):
yield number * number
for value in tqdm(squares(1000), total=1000, desc="Calculating"):
passThe Python itertools guide covers other lazy iteration patterns that pair well with progress reporting.
Manual Progress Updates
Not every workflow is a simple for loop. Create a bar with total and call update(amount) after each completed unit.
import time
from tqdm import tqdm
steps = ["read", "validate", "transform", "save"]
with tqdm(total=len(steps), desc="Pipeline", unit="step") as progress:
for step in steps:
time.sleep(0.5)
progress.set_postfix(current=step)
progress.update(1)The context manager closes the bar even if the block exits early. Update only after the corresponding work is complete, or the display may overstate progress.
Display Changing Metrics
set_postfix() adds values to the right side of the bar:
errors = 0
with tqdm(range(100), desc="Validating") as progress:
for number in progress:
if number % 17 == 0:
errors += 1
progress.set_postfix(errors=errors)Frequent formatting has a cost. Update detailed metrics periodically when the loop body is extremely fast.
Write Messages Without Breaking the Bar
Ordinary print() output can collide visually with a live progress bar. Use tqdm.write() for occasional messages:
from tqdm import tqdm
for number in tqdm(range(20), desc="Checking"):
if number == 7:
tqdm.write("Special case found at 7")For permanent records, use the Python logging module. A progress bar is temporary interface feedback; logs are durable diagnostic history.
Control Refresh Frequency
tqdm automatically limits screen updates. Important options include:
mininterval: minimum time between display refreshes.miniters: minimum completed iterations between refreshes.maxinterval: maximum time between refreshes in dynamic adjustment.disable: turn output off.leave: keep or remove the completed bar.dynamic_ncols: adapt width to the terminal.
for item in tqdm(
range(1_000_000),
mininterval=0.5,
dynamic_ncols=True,
leave=False,
):
result = item * 2A bar may be unnecessary for a loop that finishes instantly. Use progress output for work where users benefit from status information.
Disable Progress in Non-Interactive Runs
Continuous integration, redirected output, and log files may not need animated bars. One simple approach checks whether the output stream is connected to a terminal:
import sys
from tqdm import tqdm
for item in tqdm(
range(100),
disable=not sys.stderr.isatty(),
):
passYou can also make a CLI flag such as --no-progress and pass its value to disable.
Nested Progress Bars
Use position when displaying more than one bar:
import time
from tqdm import tqdm
for batch in tqdm(range(3), desc="Batches", position=0):
for item in tqdm(
range(20),
desc=f"Batch {batch + 1}",
position=1,
leave=False,
):
time.sleep(0.02)Nested bars can be helpful, but too many levels create clutter. Prefer one overall bar and one current-task bar.
Process Files with pathlib
from pathlib import Path
from tqdm import tqdm
source = Path("documents")
text_files = list(source.glob("*.txt"))
for path in tqdm(text_files, desc="Reading", unit="file"):
content = path.read_text(encoding="utf-8")
word_count = len(content.split())
print(path.name, word_count)Converting the glob result to a list gives tqdm a total. For a huge directory tree, materializing every path may use unnecessary memory; count in a separate pass only when an accurate total is worth the cost. The Python pathlib guide covers safe path operations.
Use tqdm with pandas
tqdm can register progress_apply() with pandas:
import pandas as pd
from tqdm.auto import tqdm
tqdm.pandas(desc="Normalizing")
data = pd.DataFrame({"name": [" Ava ", "NOAH", " mia"]})
data["clean_name"] = data["name"].progress_apply(
lambda value: value.strip().title()
)
print(data)Use vectorized pandas operations when possible because they are often faster than apply(). A progress bar improves visibility; it does not make an inefficient operation faster.
Use tqdm.auto for Terminals and Notebooks
tqdm.auto selects an appropriate frontend in many environments:
from tqdm.auto import tqdm
for item in tqdm(range(100)):
passIn Jupyter environments, widget support may require the notebook environment and related packages to be configured correctly.
Download a File with a Progress Bar
The following example uses urllib.request from the standard library. It reads the response in chunks and updates by the number of bytes written.
from pathlib import Path
from urllib.request import urlopen
from tqdm import tqdm
def download_file(url, destination, chunk_size=64 * 1024):
destination = Path(destination)
with urlopen(url, timeout=30) as response:
total = int(response.headers.get("Content-Length", 0))
with destination.open("wb") as file, tqdm(
total=total or None,
unit="B",
unit_scale=True,
unit_divisor=1024,
desc=destination.name,
) as progress:
while chunk := response.read(chunk_size):
file.write(chunk)
progress.update(len(chunk))
# Use a URL you trust and a suitable destination path.
# download_file("https://example.com/file.zip", "file.zip")A missing Content-Length header means the total is unknown. The bar can still show transferred bytes and speed without a percentage.
Complete Project: File Processing CLI
This script counts lines in text files and supports an optional quiet mode.
import argparse
import sys
from pathlib import Path
from tqdm import tqdm
def count_lines(path):
with path.open("r", encoding="utf-8", errors="replace") as file:
return sum(1 for _ in file)
def find_text_files(directory, recursive=False):
pattern = "**/*.txt" if recursive else "*.txt"
return sorted(directory.glob(pattern))
def build_parser():
parser = argparse.ArgumentParser(
description="Count lines in text files."
)
parser.add_argument("directory", type=Path)
parser.add_argument(
"--recursive",
action="store_true",
help="Search subdirectories.",
)
parser.add_argument(
"--no-progress",
action="store_true",
help="Disable the progress bar.",
)
return parser
def main():
args = build_parser().parse_args()
if not args.directory.is_dir():
raise SystemExit(f"Not a directory: {args.directory}")
files = find_text_files(args.directory, args.recursive)
disable_progress = args.no_progress or not sys.stderr.isatty()
total_lines = 0
for path in tqdm(
files,
desc="Counting",
unit="file",
disable=disable_progress,
):
total_lines += count_lines(path)
print(f"Files: {len(files)}")
print(f"Lines: {total_lines}")
if __name__ == "__main__":
main()Run it with:
python line_counter.py documents --recursiveCommon Mistakes
- Wrapping
enumerate(items)without passing a total when tqdm cannot infer one. Preferenumerate(tqdm(items))or providetotal=len(items). - Calling
update()with the final total instead of the amount completed since the previous update. - Using
print()repeatedly while a bar is active. - Creating a new progress bar inside every iteration.
- Materializing a huge generator only to calculate its length.
- Leaving animated output enabled in a file or non-interactive log.
- Assuming a progress bar improves processing performance.
Frequently Asked Questions
Is tqdm part of the standard library?
No. Install it from PyPI with python -m pip install tqdm.
Why does my bar have no percentage?
A percentage requires a known total. Pass total when you know the expected amount.
Can tqdm show bytes?
Yes. Use unit="B", unit_scale=True, and update by the number of bytes processed.
Can I use it in Jupyter?
Yes. tqdm.auto or tqdm.notebook can provide notebook-oriented output when widget support is available.
How do I hide the bar?
Pass disable=True, or connect it to a CLI option or terminal detection.
Does tqdm work with multiprocessing or asyncio?
The project provides helpers and patterns for concurrent and asynchronous tasks. Coordinate output carefully because several workers writing to one terminal can become difficult to read.
Conclusion
Python tqdm makes long operations easier to understand without changing their core logic. Wrap a known iterable for the simplest case, use a manual total for chunked work, label the unit clearly, and disable animated output where it does not belong.
A good progress bar reports real completed work and remains separate from durable logs. With those rules, tqdm improves command-line usability while keeping processing code readable.






