How to add a delay in Python

Learn how to delay execution in Python. Find various methods, tips, real-world applications, and how to debug common errors.

How to add a delay in Python
Published on: 
Tue
Mar 3, 2026
Updated on: 
Wed
Mar 4, 2026
The Replit Team Logo Image
The Replit Team

To pause a Python script is a common need for tasks like rate limits or timed events. The time module, with its sleep() function, offers a simple way to add delays.

In this article, we'll explore techniques to implement delays effectively. You'll find practical tips, see real-world applications, and get advice to debug common issues you might face along the way.

Basic delay with time.sleep()

import time

print("Starting...")
time.sleep(2)  # Pauses execution for 2 seconds
print("Resumed after 2 seconds")--OUTPUT--Starting...
Resumed after 2 seconds

The time.sleep() function is the simplest way to pause your script. When you call time.sleep(2), you're instructing the program to halt the current thread for two seconds. This is a "blocking" operation, which means it freezes the program's execution until the timer runs out.

This method is ideal for straightforward delays where you don't need the program to perform other tasks simultaneously. The function also accepts floating-point numbers, allowing for more precise delays like time.sleep(0.5) for half a second.

Alternative basic delay techniques

Beyond the straightforward time.sleep(), you can build more dynamic and specialized delays for asynchronous tasks, simple timers, or even entire functions.

Using asyncio.sleep() for asynchronous delays

import asyncio

async def main():
   print("Starting...")
   await asyncio.sleep(2)  # Non-blocking sleep
   print("Resumed after 2 seconds")

asyncio.run(main())--OUTPUT--Starting...
Resumed after 2 seconds

Unlike its counterpart in the time module, asyncio.sleep() is non-blocking. This means it pauses the current task without freezing the entire program. While one part of your code waits, the event loop can run other tasks in the background.

  • It's designed for asynchronous programming, which is why you see it used with the async and await keywords.
  • This makes it perfect for applications that need to handle multiple operations concurrently, like web servers or network clients.

Creating a simple countdown timer

import time

def countdown(seconds):
   for i in range(seconds, 0, -1):
       print(f"{i}...")
       time.sleep(1)
   print("Done!")

countdown(3)--OUTPUT--3...
2...
1...
Done!

You can build a simple timer by combining a for loop with time.sleep(). The countdown() function demonstrates this by iterating backward from a given number of seconds, pausing at each step to create a user-facing timer.

  • The key is the range(seconds, 0, -1) function, which generates the sequence of numbers for the countdown.
  • Inside the loop, time.sleep(1) creates the one-second delay between each number printed to the console, making the timer tick down in real-time.

Using decorators for function delays

import time
from functools import wraps

def delay_execution(seconds):
   def decorator(func):
       @wraps(func)
       def wrapper(*args, **kwargs):
           time.sleep(seconds)
           return func(*args, **kwargs)
       return wrapper
   return decorator

@delay_execution(2)
def greet():
   print("Hello, world!")

greet()--OUTPUT--Hello, world!

Decorators provide an elegant way to modify a function's behavior. The delay_execution decorator creates a wrapper that introduces a pause. When you apply @delay_execution(2) to the greet function, you're essentially swapping greet with a new version that waits before running.

  • This new version first calls time.sleep(2) to pause execution.
  • After the delay, it proceeds to run the original greet function's code.

It’s a powerful technique for separating concerns and keeping your code organized.

Advanced delay and timing techniques

Moving beyond simple pauses, you'll find that advanced techniques using threading, events, and performance counters give you more precise control over your script's timing.

Non-blocking delays with threading

import threading
import time

def delayed_task():
   print("Task will execute after 2 seconds")

timer = threading.Timer(2.0, delayed_task)
timer.start()
print("Main thread continues executing...")--OUTPUT--Main thread continues executing...
Task will execute after 2 seconds

The threading.Timer class lets you run a task after a delay without blocking your main program. It achieves this by creating a new thread that waits for a specified time before executing a function. This approach is ideal when your script needs to stay responsive while waiting.

  • You create a Timer object with a delay and a target function, like threading.Timer(2.0, delayed_task).
  • Calling timer.start() kicks off the countdown in the background.

Because it’s non-blocking, your main script continues running immediately, which is why you see its output first.

Using event-driven delays

import threading

event = threading.Event()
print("Starting...")
threading.Thread(target=lambda: event.wait(2) or print("Resumed after 2 seconds")).start()
print("Main thread continues...")--OUTPUT--Starting...
Main thread continues...
Resumed after 2 seconds

Using a threading.Event object is a sophisticated way to manage delays between threads. It works like a signal flag that one thread can raise and others can wait for. Here, a new thread calls event.wait(2), which pauses only that thread for up to two seconds. This allows your main program to continue its tasks without interruption.

  • This method is powerful because the delay is event-driven. Another thread could call event.set() to end the wait early, making it more flexible than a simple timer.
  • If the event isn't set within the timeout, event.wait() finishes, and the thread resumes its work.

Precise timing with performance counters

import time

start = time.perf_counter()
time.sleep(2)
elapsed = time.perf_counter() - start
print(f"Elapsed time: {elapsed:.6f} seconds")--OUTPUT--Elapsed time: 2.000123 seconds

When you need to measure how long a piece of code takes to run, time.perf_counter() is your most accurate tool. It provides a high-resolution clock value that’s perfect for benchmarking short durations. You simply capture the value before and after an operation, then find the difference to get the elapsed time.

  • Unlike other timing functions, it isn't affected by changes to the system clock, making your measurements more reliable.
  • The example shows that time.sleep(2) doesn't pause for exactly two seconds, which highlights the real-world precision you get with perf_counter().

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

The delay techniques from this article can be turned into production-ready tools with Replit Agent:

  • Build a web scraper that uses time.sleep() to add polite delays between requests and avoid rate limits.
  • Create a dashboard that fetches data from multiple APIs concurrently, using asyncio.sleep() to manage non-blocking waits.
  • Deploy a task scheduler that runs background jobs at specific intervals, leveraging threading.Timer for non-blocking execution.

Describe your app idea, and Replit Agent will write the code, test it, and handle deployment automatically, turning your concept into a working application.

Common errors and challenges

While adding delays is straightforward, you might run into subtle issues with timing drift, interruptions, and precision.

Dealing with cumulative delays in time.sleep() loops

When using time.sleep() inside a loop, you might notice your timing slowly drifts. This happens because the code running between each pause also takes time to execute, creating a cumulative delay that grows with each iteration.

  • This "timing drift" can be a problem in applications that need to perform actions at precise, regular intervals.
  • To fix this, you can calculate the next target execution time and sleep only for the remaining duration. This ensures your loop stays synchronized with the clock, rather than just pausing for a fixed amount of time.

Fixing KeyboardInterrupt handling with time.sleep()

A script paused with time.sleep() can be tricky to stop cleanly. If a user presses Ctrl+C, the resulting KeyboardInterrupt will stop the sleep, but it might also crash your program before it can finish important cleanup tasks.

  • You can solve this by wrapping your time.sleep() call in a try...except KeyboardInterrupt block.
  • This structure allows you to "catch" the interruption, giving you a chance to run final code—like saving data or closing connections—for a graceful shutdown.

Debugging timer precision with time.sleep()

You might find that time.sleep(1) doesn't pause for exactly one second. The function only guarantees it will pause for at least the duration you specify, but the actual time can be slightly longer due to how the operating system schedules tasks.

  • This imprecision is usually negligible for simple scripts, but it's a critical detail for applications needing high-accuracy timing.
  • If you need to measure performance or verify delays, rely on time.perf_counter() to get a true reading of the elapsed time, as it's designed for high-resolution measurements.

Dealing with cumulative delays in time.sleep() loops

Using time.sleep() in a loop can cause timing to drift because the code's own execution time adds up with each pass. This cumulative error makes the loop run longer than intended. The code below shows how this small drift accumulates.

import time

start = time.time()
for i in range(3):
   print(f"Iteration {i}")
   time.sleep(1)
   # Do some processing
   time.sleep(1)

print(f"Total time: {time.time() - start:.2f} seconds")

The total runtime is longer than the sum of the time.sleep() calls because the loop's own processing adds extra time with each pass. The code below shows how to compensate for this drift.

import time

start = time.time()
target_interval = 2  # Total time each iteration should take

for i in range(3):
   iteration_start = time.time()
   print(f"Iteration {i}")
   
   # Do some processing
   time.sleep(1)
   
   elapsed = time.time() - iteration_start
   remaining = max(0, target_interval - elapsed)
   time.sleep(remaining)

print(f"Total time: {time.time() - start:.2f} seconds")

This corrected approach ensures each loop iteration takes a consistent amount of time. Instead of a fixed pause, it calculates the exact sleep duration needed to meet a target_interval.

  • It measures how long your processing code takes to run.
  • Then, it subtracts that duration from the target to find the remaining time to sleep.

This keeps your timing precise, which is vital for applications like data loggers or schedulers that rely on regular intervals.

Fixing KeyboardInterrupt handling with time.sleep()

When a script is paused with time.sleep(), pressing Ctrl+C triggers a KeyboardInterrupt that can abruptly terminate your program. This prevents cleanup tasks, like saving data, from running. The code below demonstrates a common but flawed attempt to handle this.

import time

print("Starting a long process...")
try:
   time.sleep(30)  # Very long sleep
   print("Process completed")
except:
   print("An error occurred")

The bare except: clause is too broad, catching every error, not just a KeyboardInterrupt. This can hide other bugs and makes debugging difficult. The following example demonstrates a more precise way to handle interruptions.

import time

print("Starting a long process...")
try:
   time.sleep(30)  # Very long sleep
   print("Process completed")
except KeyboardInterrupt:
   print("Process interrupted by user")
except Exception as e:
   print(f"An error occurred: {e}")

This corrected approach specifically catches KeyboardInterrupt, allowing your script to handle user interruptions without masking other bugs. By separating it from a general except Exception block, you can perform cleanup tasks when a user presses Ctrl+C while still logging other unexpected errors.

  • This is vital for any script with long pauses or background processes that need to shut down cleanly.

Debugging timer precision with time.sleep()

You might assume time.sleep() offers exact timing, but it only guarantees a minimum pause. The operating system's task scheduling often adds extra milliseconds, a problem that becomes obvious with very short delays. The following code highlights this variability in action.

import time

def precise_delay(seconds):
   time.sleep(seconds)

# Attempting precise 10ms delays
for _ in range(5):
   start = time.time()
   precise_delay(0.01)
   print(f"Actual delay: {(time.time() - start)*1000:.2f}ms")

This example reveals the imprecision of time.sleep() for very short intervals, where the actual pause is consistently longer and more variable than requested. The code below demonstrates a more accurate method for fine-grained delays.

import time

def precise_delay(seconds):
   start = time.perf_counter()
   while time.perf_counter() - start < seconds:
       pass  # Busy waiting for very short intervals

# More precise 10ms delays
for _ in range(5):
   start = time.perf_counter()
   precise_delay(0.01)
   print(f"Actual delay: {(time.perf_counter() - start)*1000:.2f}ms")

This corrected approach creates a “busy-wait” loop for more precise timing. Instead of sleeping, the while loop continuously checks a high-resolution clock, time.perf_counter(), until the exact duration has passed. This gives you much tighter control over very short delays where accuracy is critical. Be mindful that this method is CPU-intensive, so you should only use it for brief intervals where precision is more important than efficiency.

Real-world applications

Moving past common pitfalls, you can use these delay techniques to build practical tools that manage API requests and scrape websites responsibly.

Using time.sleep() for API rate limiting

When working with APIs, you often need to control how frequently you make requests to avoid hitting rate limits, and time.sleep() offers a simple way to add a polite pause between each call.

import time
import random

def api_request(endpoint):
   # Simulate API call
   print(f"Requesting data from {endpoint}")
   return f"Data from {endpoint}"

# Make 3 rate-limited API requests
for i in range(3):
   response = api_request(f"api/endpoint/{i}")
   print(f"Received: {response}")
   time.sleep(1)  # Limit to 1 request per second

This script simulates making sequential calls to an API. Inside the for loop, the api_request() function is called, and then the program is intentionally paused.

  • The time.sleep(1) call introduces a one-second delay after each simulated request completes.
  • This creates a predictable interval between your calls, spacing them out evenly.

This approach is useful for building a steady, predictable rhythm into your script's network activity.

Building a polite web scraper with adaptive delays

Instead of a fixed pause, a polite scraper uses randomized delays to make its request pattern less predictable and more respectful of the server it's accessing.

import time
import random

def fetch_page(url):
   print(f"Fetching content from {url}")
   # Simulate network latency
   processing_time = random.uniform(0.1, 0.5)
   time.sleep(processing_time)
   return f"Content from {url}"

websites = ["example.com/page1", "example.com/page2", "example.com/page3"]
for site in websites:
   content = fetch_page(site)
   print(f"Processed {len(content)} characters")
   # Adaptive delay based on site response time
   delay = random.uniform(1.0, 3.0)
   print(f"Waiting {delay:.2f}s before next request")
   time.sleep(delay)

This script demonstrates how to introduce variable delays while processing a list of items. The fetch_page() function simulates a network request and includes a small, random pause to mimic latency before returning its content.

  • After each page is processed, the main loop generates a new random delay between one and three seconds using random.uniform(1.0, 3.0).
  • The time.sleep() function then pauses the script for that duration, creating a variable wait time before the next request is made.

Get started with Replit

Turn these delay techniques into a real tool. Just tell Replit Agent to “build a website status checker that pings URLs every five minutes” or “create a script that pulls API data with a two-second delay between requests.”

It’ll write the code, test for errors, and deploy your application from that single prompt. Start building with Replit.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.