2025, Oct 25 19:00

Tkinter Progress Bar Not Responding? Skip time.sleep and Drive Updates with after()

Learn why Tkinter progress bars freeze when time.sleep blocks the event loop, and fix it with after(). Code shows responsive UI updates without hangs.

Building small, reusable UI pieces is a great habit, but GUI code has one unforgiving rule: don’t block the event loop. A classic example is a Tkinter progress window that turns into “Not Responding” the moment you move it or switch focus, even though the underlying script keeps running. Below is a minimal illustration and a straightforward fix.

Problem: a progress bar that freezes while doing work

The window stops repainting and processing events because the main thread is stuck in a loop with sleep. The program logic keeps running, but the UI can’t breathe.

from tkinter import *
from tkinter.ttk import *
from datetime import *
import time

def boot():
    started_at = datetime.now().replace(microsecond=0)
    print(started_at, "\n")

    total_units = 100
    root = Tk()
    pct_var = StringVar()
    status_var = StringVar()
    left_var = StringVar()
    done = 0
    step = 5

    pct_lbl = Label(root, textvariable=pct_var).pack()
    status_lbl = Label(root, textvariable=status_var).pack()
    left_lbl = Label(root, textvariable=left_var).pack()

    prog = Progressbar(root, orient=HORIZONTAL, length=300)
    prog.pack(pady=10)

    while (done < total_units):
        done += step
        time.sleep(1)
        prog['value'] += (step/total_units) * 100
        pct_var.set(str(int((done/total_units)*100)) + "%")
        status_var.set(str(done) + " files completed!")
        left_var.set(str(int(total_units - done)) + " left to complete.")
        root.update_idletasks()

    root.mainloop()

if __name__ == '__main__':
    boot()

Why it happens

Using a loop together with time.sleep() in the main thread blocks Tkinter’s mainloop. While the loop is sleeping or working, pending updates and user events cannot be processed, so the OS marks the window as unresponsive. Calling update_idletasks() is not enough because the code never yields control back to the mainloop consistently.

Solution: schedule work with .after()

Instead of looping and sleeping, schedule periodic updates using .after(). This keeps the event loop free to process redraws and user actions, and your progress bar remains responsive.

from tkinter import *
from tkinter.ttk import *
from datetime import *
import time

def boot():
    started_at = datetime.now().replace(microsecond=0)
    print(started_at, "\n")

    total_units = 100
    root = Tk()
    pct_var = StringVar()
    status_var = StringVar()
    left_var = StringVar()
    done = 0
    step = 5

    pct_lbl = Label(root, textvariable=pct_var).pack()
    status_lbl = Label(root, textvariable=status_var).pack()
    left_lbl = Label(root, textvariable=left_var).pack()

    prog = Progressbar(root, orient=HORIZONTAL, length=300)
    prog.pack(pady=10)

    def tick(current):
        progress = round((current / total_units) * 100, 0)
        prog['value'] = progress
        pct_var.set(f'{progress:.0f}%')
        status_var.set(f'{current} files completed!')
        left_var.set(f'{total_units - current} left to complete.')
        if current < total_units:
            root.after(1000, tick, current + step)
        if current == total_units:
            quit()

    tick(done)
    root.mainloop()

if __name__ == '__main__':
    boot()

Why this matters

GUI frameworks are event-driven. If you block the mainloop with a sleep or a long-running loop, the UI cannot process input or redraw, which leads to a frozen window and a poor user experience. Using .after() to simulate a loop preserves responsiveness while keeping the logic simple and reusable.

Takeaways

Keep the Tkinter main thread free from blocking loops and time.sleep(). Use .after() to schedule periodic UI updates and background-like progress ticks. If you want the program to exit when the work is done, call quit() once the progress reaches its terminal state.

The article is based on a question from StackOverflow by Lee Donovan and an answer by acw1668.