Library / SDK
AnswerDotAI/fastprogress avatar
AnswerDotAI/fastprogress

fastprogress: Nested Bars and Live Loss Plots Inside the Training Loop

Simple and flexible progress bar for Jupyter Notebook and console

1,098 stars104 forksJupyter NotebookApache-2.0

At a glance

What is it?
AnswerDotAI's fastprogress pairs a nested master/child progress bar with an inline matplotlib graph that updates during a loop. It is small, Apache-2.0 licensed, and built for one job: making an epoch loop readable in Jupyter and in the terminal.
Who is it for?
Adopt fastprogress if your outer loop is an epoch loop and you want a child bar plus a live loss curve rendered from inside that loop, since update_graph is the part no plain progress bar gives you. Do not adopt it if you need output that survives a redirect to a file, or if you want a bar you can drop into arbitrary code without restructuring the loop around a master_bar object.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly Jupyter Notebook, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem fastprogress targets: an epoch loop that needs two levels of feedback

Most progress bars assume one flat iterable. A training loop is not flat. There is an outer loop over epochs and an inner loop over batches, and the useful information sits at different levels: batch throughput on the inner bar, epoch-level loss on the outer one. fastprogress models that directly. The README's first example builds a master_bar over range(10) and then a nested bar with mb.progress(range(100)), where the child is attached by passing parent=mb. Each level carries its own comment string, set through mb.main_bar.comment and mb.child.comment, so a batch loop can report a per-batch statistic while the epoch loop reports something else. The mb.write('message') call prints a line between the two bars, which is where per-epoch summaries belong. The audience is narrow and identifiable: people writing training loops in notebooks who want the loop's shape visible on screen, and who are willing to structure the loop around a master_bar object to get it. The second example extends the same object with plotting, which is the reason to pick this over a general-purpose bar.

How master_bar and progress_bar fit together, and where update_graph sits

The mechanism visible in the README is delegation. master_bar is created over an iterable and returns an object, here bound as mb. That object exposes .progress(), which yields a child bar tied to the parent, and also exposes .child and .main_bar so the loop body can write comments into either level. The child bar is itself a progress bar over its own iterable, so the nesting is one level deep by construction rather than by a general tree of bars. The second mechanism is the graph. mb.update_graph(graphs, x_bounds, y_bounds) takes a list of graphs, each of the form [x, y], and creates the figure on first use. Two optional arguments set the axis limits. The README is explicit about why you should pass them: without x_bounds and y_bounds the box changes as the loop progresses, which means the curve appears to move under you rather than growing in place. Graph labels come from mb.names, which should have as many elements as there are graphs. In the README's second example the names are ['cos', 'sin'] and the graphs list holds two [x, y] pairs, so the label count and the graph count line up. The third example shows the pattern that matters for training: a helper that recomputes y_bounds from the current minimum and maximum of the accumulated loss arrays, with a small margin, so the plot rescales as losses drop instead of being clipped by a fixed window.

Installing it and the exact calls in a working loop

Installation is one command: pip install fastprogress. The import in every README example is a star import from fastprogress.fastprogress, which brings master_bar and progress_bar into scope. The minimal loop from the README is a master_bar over range(10) with an inner mb.progress(range(100)), setting mb.child.comment inside the inner loop and mb.main_bar.comment after it, then calling mb.write with a formatted string. The plotting loop adds names=['cos', 'sin'] to the master_bar constructor and calls mb.update_graph(graphs, x_bounds, y_bounds) with x_bounds set to [0, 2*np.pi] and y_bounds to [-1, 1]. The training example wraps this in a function, plot_loss_update(epoch, epochs, mb, train_loss, valid_loss), which builds x as range(1, epoch+1), concatenates the train and validation loss lists, and derives y_bounds from np.min and np.max of that concatenation with a 0.05 margin and an x margin of 0.2. Note the docstring's constraint: the helper expects epoch to start from 1. That is a real coupling between your loop counter and the plotting helper, not a cosmetic detail. The emulation loop then runs two child bars per epoch, one for training and one for validation, both created with progress_bar(range(2), parent=mb), and calls the helper at the end of each epoch.

Console, notebook, and the redirect behaviour that will surprise you

The README states that the same code renders in the console, and it includes a console rendering of the graphing example. It also documents one behaviour that is easy to miss and hard to debug: if the script using fastprogress is executed with a redirect to a file, only the results of the .write method will be printed in that file. Bars and graphs do not survive the redirect. If you run a training job as python train.py > log.txt, your log will contain the epoch summary lines you emitted through mb.write and nothing else. That is arguably the correct design, since carriage-return bar updates would otherwise fill a log with thousands of partial lines, but it means any diagnostic you set as a comment on a bar rather than writing out will be absent from the file. The practical consequence is that per-batch statistics belong in mb.child.comment for live viewing and, if they need to be recorded, in a separate logging call. The README does not describe a way to change this behaviour.

The plotting path is the reason to choose it, and also its main constraint

A plain progress bar tells you how far along a loop is. fastprogress additionally draws the loop's history. In the training example, update_graph is called once per epoch with the full accumulated loss arrays, so the curve is redrawn rather than appended to. That has a cost profile worth understanding before you adopt it: the work per update scales with the number of epochs completed, because the whole x and y arrays are passed each time. For a five-epoch emulation this is irrelevant. For a loop with thousands of epochs it is a per-epoch cost that grows linearly, and the README gives no guidance on where that becomes noticeable. The dynamic y_bounds helper has a related property: it recomputes the axis range from the current data every call, which is what keeps the curve visible, but it also means the axis rescales mid-run, so the visual slope between two epochs is not directly comparable across a rescale. The README's own advice to pass fixed bounds when you can is the alternative, and the trade-off is clipping versus comparability. Neither option is wrong; the point is that you are choosing.

Where fastprogress is the wrong tool

The nesting is one level deep and the API is built around a master_bar instance threaded through the loop. If your code has three nested loops, or if the outer loop lives in a different function from the inner one, you will be passing mb around or restructuring to keep the reference in scope. Libraries that instrument loops from the outside do not impose that. The redirect behaviour is the second boundary: if your deployment captures stdout to a file or a log aggregator and you expect the bar's state to be there, it will not be. Third, the README documents no environment detection or configuration for choosing between notebook and console rendering; it presents both as outputs of the same code, and does not describe what happens in editors, CI logs, or terminal multiplexers. If your environment is not one of the two the README shows, you are outside the documented surface. Finally, the graph is a matplotlib figure embedded by the library. If your loop already owns its plotting, or if you are running headless, the graphing half of the library is dead weight and you are using it as a bar with an unusual API.

tqdm as the alternative, and the actual difference in approach

tqdm is the obvious comparison and the difference is structural rather than cosmetic. tqdm wraps an iterable and returns an iterator: you write for x in tqdm(items) and the rest of the loop is unchanged. It does not require an object to be created beforehand, threaded through nested calls, or referenced from the loop body to set comments. fastprogress inverts that. You create master_bar first, bind it, iterate it, and reach back into it through .child, .main_bar, .progress, and .write. The payoff for that inversion is update_graph, which draws a live plot from inside the loop. tqdm's documented feature set in this comparison does not include an inline loss plot, so if the graph is what you want, the restructuring is the price of it. If the graph is not what you want, the restructuring buys you nothing and a plain iterator wrapper is less invasive. There is a third option worth naming: logging. A logger writing one line per epoch gives you a durable record under redirect, which fastprogress explicitly does not, at the cost of any live display. Pick based on whether the loop's history needs to be watched or recorded.

Licence, maintenance, and what to check before adopting

fastprogress is Apache-2.0 licensed, which permits commercial and private use and includes an express grant of patent rights, with the usual requirements around retaining notices and stating changes. That is a permissive licence and it is not a reason to hesitate; this is not legal advice and if you redistribute a modified copy you should read the licence text yourself. On maintenance, the repository is not archived and the release history shows 1.1.3 in late December 2025, 1.1.5 in February 2026, and 1.1.6 in May 2026, with the last push to the default branch in September 2026. That pattern suggests a small library receiving occasional point releases rather than active feature development, which is consistent with its scope. The upgrade cost is low by design: the public surface is a handful of names from fastprogress.fastprogress, and the README's three examples exercise most of it. The thing to verify before you commit is not the API but the rendering. Open a notebook, run the second README example, and confirm that update_graph draws where you expect and that the figure does not fight with any other plotting you do in the same cell. Then run your training script once with output redirected to a file and confirm that the lines you care about are the ones you routed through mb.write.

Editorial conclusion

Adopt fastprogress if your outer loop is an epoch loop and you want a child bar plus a live loss curve rendered from inside that loop, since update_graph is the part no plain progress bar gives you. Do not adopt it if you need output that survives a redirect to a file, or if you want a bar you can drop into arbitrary code without restructuring the loop around a master_bar object. Before committing, verify two things on your own setup: that update_graph renders acceptably in your notebook front end, and that your logging path does not depend on stdout, because the README states that under a redirect only .write output reaches the file.

Official sources

  1. AnswerDotAI/fastprogress on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Community notes

Community notes