Python sched: Schedule Events

Published on: August 28, 2026
Reading time: 5 minutes
A minimalist June calendar page adorned with red hearts for a romantic touch.

The sched module implements a simple in-memory event scheduler. It maintains a queue ordered by time and priority, waits until events are due, and executes callbacks. It is useful in scripts, simulations, tests, small services, and applications that need local timing without an external dependency.

sched is not cron, a distributed queue, or durable job infrastructure. Events disappear when the process exits, long callbacks delay later work, and the module does not provide retries, persistence, or multi-machine execution. Use it for controlled local scheduling.

Create a scheduler

The constructor accepts a time function and a delay function. Modern defaults are based on time.monotonic and time.sleep.

import sched
import time

scheduler = sched.scheduler(time.monotonic, time.sleep)

A monotonic clock is appropriate for intervals because it does not move backward when wall-clock time changes.

Schedule with a relative delay

enter() schedules an action after a delay.

def run_task(name):
    print("running", name)

scheduler.enter(5, 1, run_task, argument=("task",))
scheduler.run()

The second argument is priority. Lower numbers run first when events share the same timestamp.

Arguments and keyword arguments

Use argument for positional parameters and kwargs for named parameters.

scheduler.enter(
    2,
    1,
    send,
    argument=(destination,),
    kwargs={"attempt": 1},
)

Explicit arguments are easier to test and log than closures that capture mutable state.

Schedule at an absolute time

enterabs() receives a value compatible with the scheduler’s time function.

when = time.monotonic() + 10
scheduler.enterabs(when, 1, run_task, argument=("absolute",))

Do not mix time.time() timestamps with a scheduler based on time.monotonic().

Priorities

When events have the same due time, priority controls their order.

scheduler.enter(1, 10, run_task, argument=("normal",))
scheduler.enter(1, 1, run_task, argument=("urgent",))

Priority does not preempt a callback that is already running.

The Event object

enter() and enterabs() return an event object that can later be cancelled.

event = scheduler.enter(30, 1, run_task)

Store the reference alongside the application’s logical task identifier.

Cancel an event

cancel() removes a pending event.

try:
    scheduler.cancel(event)
except ValueError:
    print("the event is no longer pending")

Cancellation fails when the event has already executed, was removed, or does not belong to the queue.

Inspect the queue

The queue property exposes pending events in execution order.

for event in scheduler.queue:
    print(event.time, event.priority, event.action)

Use it for observability, not for direct mutation.

Run without blocking for the next event

run(blocking=False) executes due events and returns information about the next deadline when one exists.

next_deadline = scheduler.run(blocking=False)

This mode helps integrate the scheduler into a GUI, main loop, or service that performs other work.

Long callbacks

The scheduler runs callbacks sequentially. A ten-second action delays every event that becomes due during those ten seconds.

Keep callbacks short or submit work to a bounded executor. Do not create an unlimited thread per event.

Late events

When the process is busy, sched does not automatically drop overdue events. It executes them as soon as possible in queue order.

The application must decide whether stale work still matters. Compare planned and actual time and define a tolerance.

Recurring tasks

A callback can schedule its next occurrence.

INTERVAL = 60


def recurring(next_time):
    perform_work()
    following = next_time + INTERVAL
    scheduler.enterabs(following, 1, recurring, argument=(following,))

first = time.monotonic() + INTERVAL
scheduler.enterabs(first, 1, recurring, argument=(first,))

Calculating from the planned time reduces drift.

Avoid drift

If a callback calls enter(INTERVAL, ...) only after finishing, task duration is added to every cycle.

Use absolute planned times for a fixed cadence. Use relative delays only when the rule is “wait N seconds after completion.”

Callback failures

An exception escapes from run(). The scheduler remains structurally consistent, but later events wait until the caller invokes run() again.

def safe_action():
    try:
        perform_work()
    except Exception:
        logger.exception("scheduled action failed")

Catch errors only when there is a clear policy. Critical failures may need to terminate the process.

Retries

sched does not retry automatically.

Retry only transient failures, with limits, backoff, jitter, and idempotency. Do not retry validation errors forever.

Threads

Modern implementations allow scheduling from multiple threads, but the application still needs clear lifecycle, cancellation, and callback rules.

Do not hold application locks while callbacks execute if those callbacks may acquire the same resources.

Earlier events inserted by another thread

If one thread is waiting for a distant event and another inserts an earlier one, the outer integration must ensure the loop rechecks the queue.

For complex concurrent systems, a condition-based loop, asyncio, or a dedicated scheduler may be a better fit.

Integrate with ThreadPoolExecutor

The scheduler can submit work to a bounded pool.

from concurrent.futures import ThreadPoolExecutor

executor = ThreadPoolExecutor(max_workers=4)
scheduler.enter(1, 1, executor.submit, argument=(perform_work,))

Observe returned futures and shut the executor down cleanly. See Python concurrent.futures.

Shutdown

Maintain a stop flag or event. Stop creating new recurrences, cancel pending events when appropriate, and wait for submitted work.

An in-memory scheduler cannot guarantee pending tasks survive shutdown.

Wall-clock schedules

For rules such as “09:00 local time,” convert civil time carefully or use infrastructure that handles time zones, daylight-saving transitions, and persistence.

time.monotonic() measures intervals; it is not a calendar timestamp.

Deterministic tests

Inject fake time and delay functions.

class Clock:
    def __init__(self):
        self.now = 0
    def time(self):
        return self.now
    def sleep(self, delay):
        self.now += delay

clock = Clock()
test_scheduler = sched.scheduler(clock.time, clock.sleep)

Tests can then advance instantly without real sleeps.

Persistence

If events must survive restarts, store job intent in a database or durable queue and rebuild local scheduling at startup.

Do not serialize arbitrary callbacks. Persist an approved task type and validated parameters.

Security

Never let users schedule arbitrary Python callables. Map approved commands to known functions.

Apply limits to task count, frequency, priority, and argument size.

Observability

Record logical task ID, planned time, actual start, delay, duration, result, and attempt.

Queue length, lateness, and failure metrics reveal blocked callbacks and capacity problems.

When to choose another tool

Use cron or Task Scheduler for independent processes, asyncio for asynchronous applications, durable queues for persistent jobs, and richer scheduling libraries for calendar rules.

sched is best for a small local time-ordered queue.

Common mistakes

Common failures include mixing clocks, running long callbacks, introducing recurring drift, assuming persistence, ignoring exceptions, forgetting cancellation, treating priority as preemption, and using real sleeps in tests.

Conclusion

sched provides local event scheduling through a time-ordered queue. Use a monotonic clock for intervals, absolute deadlines for fixed cadence, priorities for tie-breaking, and short callbacks.

Add cancellation, observability, and explicit error policy. Consult the official sched documentation and Python concurrent.futures.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Stack of colorful sticky notes with a 'To Do' note on top, isolated on white.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python bisect: Keep Lists Sorted

    Learn Python bisect for binary search, ordered insertion, duplicates, key functions, ranges, rankings, and safe concurrent updates.

    Ler mais

    Tempo de leitura: 4 minutos
    28/08/2026
    Close-up of a vibrant yellow python coiled with textured scales in vibrant light.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python heapq: Priority Queues

    Learn Python heapq for priority queues, top-k selection, sorted merges, stable ties, lazy deletion, updates, and backpressure.

    Ler mais

    Tempo de leitura: 4 minutos
    28/08/2026
    Detailed shot of a yellow measuring tape with numbers on a dark background.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python array: Compact Numeric Data

    Learn Python array for compact numeric storage, typecodes, bytes, files, memoryview, portable binary layouts, and safe validation.

    Ler mais

    Tempo de leitura: 4 minutos
    28/08/2026
    Detailed view of programming code in a dark theme on a computer screen.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python struct: Binary Data Formats

    Learn Python struct for binary packing, endianness, offsets, padding, buffers, sockets, versioned layouts, and safe validation.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    Composition of black and white photos and old travel log placed with vintage island map and obsolete photo camera on narrow wooden table
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python mmap: Memory-Mapped Files

    Learn Python mmap for memory-mapped files, byte searches, in-place edits, shared pages, aligned offsets, and safe synchronization.

    Ler mais

    Tempo de leitura: 6 minutos
    28/08/2026
    Close-up of a USB pen drive being inserted into a laptop USB port on a white surface.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python importlib.metadata: Versions and Plugins

    Learn Python importlib.metadata to query versions, requirements, files, distributions, entry points, and plugins without importing packages.

    Ler mais

    Tempo de leitura: 9 minutos
    27/08/2026