Sometimes you need to make Python sleep, wait, or pause before running the next line of code. Whether you’re spacing out API requests, pacing a thread, or adding a delay to terminal output, Python’s time.sleep() function is the standard tool:

Language: Python
fromtimeimport sleep
sleep(3)  # Pause execution for 3 seconds

Beyond time.sleep(), Python provides different ways to add time delays depending on the context, including threads, async code, and GUI applications.

By the end of this tutorial, you’ll understand that:

  • time.sleep() suspends execution for a given number of seconds, including fractional values like milliseconds.
  • Retry decorators use time.sleep() to add a delay between failed attempts.
  • Event.wait() is the preferred way to add delays in threads because it can be interrupted cleanly.
  • asyncio.sleep() pauses a single coroutine without blocking the rest of your async code.
  • GUI frameworks like Tkinter provide scheduling methods such as .after() to avoid freezing the event loop.

The following sections cover each of these approaches with working code examples.

Take the Quiz: Test your knowledge with our interactive “Python time.sleep()” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Python time.sleep()

In this quiz, you'll revisit how to add time delays to your Python programs.

Pause Execution With Python sleep()

Python has built-in support for making your program wait. The time module has a sleep() function that you can use to add a delay by suspending execution of the calling thread for the number of seconds you specify:

Language: Python
>>> importtime
>>> time.sleep(3)  # Sleep for 3 seconds

Here’s a quick example of time.sleep() in action:

Language: Python Filename: coffee.py
importtime

print("Brewing coffee...")
print("This would take like 3 secs...")
time.sleep(3)
print("Done! Your coffee is ready!")

If you run this script, you’ll see a three-second pause between the messages while time.sleep() suspends execution.

You can also pass fractional seconds to time.sleep() for finer-grained durations. Here are some common values:

Language: Python
importtime

time.sleep(0.5)  # Wait 500 milliseconds
time.sleep(0.001)  # Wait 1 millisecond
time.sleep(1.5)  # Wait 1.5 seconds
time.sleep(60)  # Wait 1 minute

The time.sleep() function isn’t perfectly precise. The specified value acts as a minimum delay. The actual pause will almost always be slightly longer in practice due to operating system scheduler overhead and current system load.

You can test how long the sleep lasts by using Python’s timeit module:

Language: Shell
$ python-mtimeit-n3"import time; time.sleep(3)"
3 loops, best of 5: 3 sec per loop

Here, you run the timeit module with the -n parameter, which tells timeit how many times to run the statement per repeat. With the default of five repeats, the statement runs 15 times in total (3 × 5). timeit then reports the best time across all repeats, which is three seconds per loop, as expected.

For a more realistic example, say you need to monitor whether a website is up. You want to check its status code periodically, but querying the server too often could overload it or get you rate-limited. You can use time.sleep() to space out the checks:

Language: Python Filename: uptime_bot.py
importtime
importurllib.request
importurllib.error

CHECK_INTERVAL = 60  # Seconds between checks

defuptime_bot(url):
    while True:
        try:
            urllib.request.urlopen(url)
        except urllib.error.HTTPError as e:
            # Email admin or log
            print(f"HTTPError: {e.code} for {url}")
        except urllib.error.URLError as e:
            # Email admin or log
            print(f"URLError: {e.reason} for {url}")
        else:
            # Website is up
            print(f"{url} is up")
        time.sleep(CHECK_INTERVAL)

if __name__ == "__main__":
    url = "https://www.google.com/py"
    uptime_bot(url)

Here, you create uptime_bot(), which takes a URL as its argument. The function then attempts to open that URL with urllib. If there’s an HTTPError or URLError, then the program catches it and prints out the error. In a live environment, you would log the error and probably send an email to the webmaster or system administrator.

If no errors occur, then your code prints a status message confirming that the site is up. Regardless of what happens, your program will sleep for 60 seconds. This means that you only access the website once every minute. The URL in this example points to a page that doesn’t exist, so it will output the following to your console once every minute:

Language: Shell
$ pythonuptime_bot.py
HTTPError: 404 for https://www.google.com/py

Go ahead and update the code to use a known good URL, like https://www.google.com. Then run it again to see it succeed. You could also extend the script to send an email or log the errors. For more information on how to do this, check out Sending Emails With Python and Logging in Python.

Wait and Retry Failed Operations With a Decorator

Sometimes a function fails on the first try but succeeds on a later attempt. For example, a file download might fail because the server is temporarily busy, or an automated UI test might fail because the interface hasn’t fully loaded yet. In both cases, adding a delay between retries with time.sleep() can solve the problem.

You can wrap this retry-with-delay pattern in a decorator so that any function gains automatic retry behavior. If you’re not familiar with decorators, or if you’d like to brush up on them, Real Python’s Primer on Python Decorators covers the syntax, common patterns, and how parameterized decorators like the one you’ll build below actually work.

Here’s an example:

Language: Python Filename: utils.py
importtime
fromfunctoolsimport wraps

defretry(delay=3, max_retries=3):
    defdecorator(function):
        @wraps(function)
        defwrapper(*args, **kwargs):
            last_exc = None
            for attempt in range(1, max_retries + 1):
                try:
                    return function(*args, **kwargs)
                except Exception as e:
                    last_exc = e
                    print(
                        f"Retrying in {delay}s... "
                        f"({attempt}/{max_retries})"
                    )
                    time.sleep(delay)
            raise last_exc
        return wrapper
    return decorator

The retry() function is a parameterized decorator. It accepts a delay value and the number of max_retries, both of which default to 3. Inside retry(), there’s another function called decorator() that receives the function to decorate.

Finally, the innermost function wrapper() accepts the arguments and keyword arguments that you pass to the decorated function. This is where the retry logic lives.

You use a for loop to call the function up to max_retries times. If there’s an exception, then you capture it as last_exc, call time.sleep(), and try running the function again. If every attempt fails, then you re-raise the last exception so the caller knows something went wrong.

Here’s how you can use it to retry a file download:

Language: Python Filename: download.py
importurllib.request
importurllib.error
fromutilsimport retry

@retry(delay=3)
defdownload_file(url):
    try:
        response = urllib.request.urlopen(url)
        print(f"Downloaded {url} ({response.length} bytes)")
    except urllib.error.URLError as e:
        print(f"Download failed: {e.reason}")
        raise

if __name__ == "__main__":
    download_file("http://www.example.com/data.csv")

Here, you decorate download_file() with @retry(delay=3) to retry up to three times with a three-second delay between attempts. If the download fails, then the decorator catches the re-raised exception, waits, and tries again. You can reuse this decorator on any function that might fail intermittently.

Notice the raise at the end of the except block. It re-raises the exception so the decorator can catch it and trigger a retry. Without it, the function would silently return, and the decorator wouldn’t know anything went wrong.

There’s still one improvement you could make: the decorator waits delay seconds after the final failed attempt before re-raising, which usually isn’t what you want. Feel free to try fixing this as an exercise!

Add Delays in Python Threads

Sometimes you need a thread to wait before continuing its work. Perhaps you’re running a migration script against a database with millions of records in production. You don’t want to cause any downtime, but you also don’t want to wait longer than necessary to finish the migration, so you decide to use threads.

To prevent customers from noticing any kind of slowdown, each thread needs to run for a short period and then sleep. The threading module provides Event.wait() for exactly this purpose.

The Event.wait() method works like time.sleep() but with the added benefit of being interruptible. You can read more about how it works in Python’s threading documentation. Here’s an example using Python’s logging module, which is thread-safe:

Language: Python Filename: thread_monitor.py
importlogging
importthreading

WORKER_INTERVAL = 1  # Seconds between worker check-ins
MAIN_INTERVAL = 0.75  # Seconds between main thread check-ins

defworker(event):
    while not event.is_set():
        logging.debug("Worker thread checking in")
        event.wait(WORKER_INTERVAL)

defmain():
    logging.basicConfig(
        level=logging.DEBUG,
        format="{relativeCreated:>6,.0f} ms | {threadName:<18} | {message}",
        style="{",
    )
    event = threading.Event()

    thread_one = threading.Thread(target=worker, args=(event,))
    thread_two = threading.Thread(target=worker, args=(event,))
    thread_one.start()
    thread_two.start()

    while not event.is_set():
        try:
            logging.debug("Main thread checking in")
            event.wait(MAIN_INTERVAL)
        except KeyboardInterrupt:
            event.set()
            break

if __name__ == "__main__":
    main()

In this example, you create a threading.Event() and pass it to worker(). Both loops check whether event is set. If it isn’t, then your code logs a debug message and waits for the configured interval before checking again. Pressing Ctrl+C triggers event.set(), which causes both the worker loops and the main loop to exit, ending the program.

As an exercise, try modifying the code so that each worker thread uses a different wait interval. You can pass the interval as an additional argument to worker().

Add Non-Blocking Delays With asyncio.sleep()

Asynchronous capabilities were first added to Python with the asyncio module in version 3.4, and the dedicated async and await syntax followed in 3.5.

Asynchronous programming is a form of concurrency that lets a single thread handle multiple tasks by cooperatively switching between them. When one task pauses to wait for something like a network response, the event loop runs another task in the meantime.

The sleep() function from the asyncio module lets you add non-blocking delays to your async code. Unlike the built-in time.sleep() function, which halts the entire thread, asyncio.sleep() only suspends the current coroutine. It yields control back to the event loop, allowing other tasks to run during the delay.

If you’re unfamiliar with Python’s implementation of asynchronous programming, then check out the tutorial Python’s asyncio: A Hands-On Walkthrough.

Here’s an example from Python’s own documentation:

Language: Python Filename: hello_async.py
importasyncio

async defmain():
    print("Hello ...")
    await asyncio.sleep(1)
    print("... World!")

if __name__ == "__main__":
    asyncio.run(main())

If you run this script, you’ll see a one-second pause between the two print() calls:

Language: Shell
$ pythonhello_async.py
Hello ...
... World!

Here’s a more realistic example. When you’re hitting an API that enforces rate limits, you need to space out your requests. Using asyncio.sleep() with aiohttp, you can add a delay between calls without blocking the rest of your program.

First, install aiohttp from PyPI:

Language: Shell
$ python-mpipinstallaiohttp

Then create the following script:

Language: Python Filename: rate_limited.py
importasyncio
importaiohttp

RATE_LIMIT_DELAY = 1  # Seconds between API requests

async deffetch_page(session, url):
    async with session.get(url) as response:
        print(f"{url} ({response.status})")
    await asyncio.sleep(RATE_LIMIT_DELAY)

async defmain():
    urls = [
        "https://httpbin.org/get",
        "https://httpbin.org/ip",
        "https://httpbin.org/headers",
    ]
    async with aiohttp.ClientSession() as session:
        for url in urls:
            await fetch_page(session, url)

if __name__ == "__main__":
    asyncio.run(main())

Each call to fetch_page() makes a real HTTP request and then waits one second before the next one, keeping you within the API’s rate limit:

Language: Shell
$ pythonrate_limited.py
https://httpbin.org/get (200)
https://httpbin.org/ip (200)
https://httpbin.org/headers (200)

Because asyncio.sleep() only suspends the current coroutine, other tasks in your event loop can keep running during each delay. If you used time.sleep() here instead, the entire event loop would freeze until each request finished.

Avoid Freezing GUIs While Adding Delays

GUI frameworks like Tkinter and wxPython run all their processing in a main thread using an event loop. If you call time.sleep() inside GUI code, you’ll block that event loop. From the user’s perspective, the application will freeze, and its buttons and widgets won’t respond. The window won’t redraw, and on Windows specifically, you might even get an “unresponsive” alert.

To see this in action, try running the following Tkinter app and clicking the button:

Language: Python Filename: frozen_app.py
importtkinter
importtime

DELAY = 3  # Sleep for 3 seconds

defon_click():
    time.sleep(DELAY)
    print("Done!")

root = tkinter.Tk()
root.geometry("400x400")
button = tkinter.Button(text="Click me!", command=on_click)
button.pack()

root.mainloop()

The button will remain pressed for three seconds while time.sleep() blocks the event loop. You won’t be able to click anything else or even close the window until time.sleep() returns.

Instead of time.sleep(), most GUI frameworks provide their own scheduling methods that add delays without blocking the event loop. In Tkinter, the solution is the .after() method, which schedules a callback to run after a certain number of milliseconds.

Here’s how you can use it to keep the GUI responsive:

Language: Python Filename: responsive_app.py
importtkinter

DELAY = 3000  # The after() method takes milliseconds (3000 ms = 3 seconds)

defon_click():
    root.after(DELAY, lambda: print("Done!"))

root = tkinter.Tk()
root.geometry("400x400")
button = tkinter.Button(text="Click me!", command=on_click)
button.pack()

root.mainloop()

The .after() method takes two arguments: the number of milliseconds to wait and the callback to run when the wait is finished. Unlike time.sleep(), it schedules the callback and returns immediately, so the event loop keeps running, and your application stays responsive. In fact, you can keep clicking the button while you wait. Each click schedules another callback, and after each delay elapses, you’ll see Done! printed once per click.

Other GUI frameworks have equivalent methods. For example, wxPython provides wx.CallLater(), which plays the same role as .after() in Tkinter. The pattern is always the same: schedule a delayed callback instead of blocking the thread with time.sleep().

Conclusion

Now you know how to make Python wait, sleep, and add time delays across a range of contexts. Whether you need a quick pause between API calls or a non-blocking delay in async code, Python has you covered.

In this tutorial, you’ve learned how to:

  • Pause execution with time.sleep() for any duration
  • Retry failed operations using a sleep-based decorator
  • Add delays in threads with time.sleep() and Event.wait()
  • Use asyncio.sleep() for non-blocking async delays
  • Avoid freezing GUIs with .after() in Tkinter and wx.CallLater() in wxPython

With these techniques, you can pace your applications, prevent them from overloading system resources, and keep your GUIs responsive, all while your code waits as long as it needs to.

If you want to keep exploring this area, Real Python’s Concurrency and Async Programming learning path features resources on threading, asyncio, and parallel processing for a structured next step.

Frequently Asked Questions

Now that you have some experience with adding time delays in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.

These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.

Call time.sleep(1) after importing the time module. This pauses execution of the current thread for one second before continuing to the next line of code.

Yes. You can do this by passing a fractional number of seconds to time.sleep(). For example, time.sleep(0.5) adds a 500-millisecond delay, and time.sleep(0.001) waits for one millisecond.

Both pause execution for a given number of seconds, but time.sleep() blocks the entire thread, while asyncio.sleep() only suspends the current coroutine. In async code, always use asyncio.sleep() so that other tasks can run during the wait.

GUI frameworks run on an event loop in the main thread. Calling time.sleep() blocks that thread, preventing the framework from processing user input or redrawing the window. Use framework-specific alternatives like Tkinter’s .after() or wxPython’s wx.CallLater() instead.

In many cases, yes. Event.wait() is interruptible. It returns immediately when the event is set, letting your thread exit cleanly. With time.sleep(), the thread must finish the full sleep duration before it can respond to a shutdown signal.

Take the Quiz: Test your knowledge with our interactive “Python time.sleep()” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Python time.sleep()

In this quiz, you'll revisit how to add time delays to your Python programs.