Skip to main content

Prefect: Modern Workflow Orchestration for Data Pipelines

Prefect is the modern Python workflow orchestration platform that treats your code as the source of truth. No DAG classes, no XML — just Python functions decorated with @flow and @task, with built-in retries, caching, scheduling, and a beautiful UI.

✓ Pure Python workflows ✓ Native retries & caching ✓ Prefect Cloud (managed UI) ✓ Dynamic task mapping

What is Prefect?

Prefect is an open-source workflow orchestration tool built for data engineers and data scientists who want a modern alternative to Apache Airflow. Released in 2018, Prefect 2.0 (Orion) fundamentally redesigned the architecture: workflows are native Python functions, dynamic control flow is supported out of the box, and the deployment model decouples scheduling from execution through Work Pools and Workers.

Prefect Cloud provides a hosted control plane with a rich UI showing flow run history, task states, logs, and scheduling — with a generous free tier. Workers run on your own infrastructure (local, Docker, Kubernetes, ECS) and poll Prefect Cloud for work. This hybrid model means your data never leaves your infrastructure while still getting the benefits of a managed orchestration backend.

Flows, Tasks & Retries

# pip install prefect
from prefect import flow, task
from prefect.tasks import task_input_hash
from datetime import timedelta
import httpx

# Tasks — individual units of work with retry/caching
@task(
    retries=3,
    retry_delay_seconds=10,
    cache_key_fn=task_input_hash,
    cache_expiration=timedelta(hours=1),
)
def fetch_data(url: str) -> dict:
    response = httpx.get(url, timeout=30)
    response.raise_for_status()
    return response.json()

@task(log_prints=True)
def transform_data(raw: dict) -> list[dict]:
    records = raw.get("data", [])
    return [
        {
            "id": r["id"],
            "value": r["metrics"]["total"] * 1.1,
            "category": r["type"].upper(),
        }
        for r in records
        if r.get("active", True)
    ]

@task
def load_to_warehouse(records: list[dict]) -> int:
    # ... write to database
    print(f"Loaded {len(records)} records")
    return len(records)

# Flow — orchestrates tasks
@flow(name="ETL Pipeline", log_prints=True)
def etl_pipeline(api_url: str = "https://api.example.com/data") -> int:
    raw = fetch_data(api_url)
    transformed = transform_data(raw)
    count = load_to_warehouse(transformed)
    return count

if __name__ == "__main__":
    result = etl_pipeline()
    print(f"Pipeline done: {result} records loaded")

Deployments & Scheduling

# Prefect Deployments — schedule flows for production
from prefect import flow
from prefect.deployments import Deployment
from prefect.server.schemas.schedules import CronSchedule

@flow
def etl_pipeline(api_url: str = "https://api.example.com"):
    ...

# Create a deployment with a cron schedule
deployment = Deployment.build_from_flow(
    flow=etl_pipeline,
    name="production-etl",
    parameters={"api_url": "https://api.example.com/v2"},
    schedule=CronSchedule(cron="0 6 * * *", timezone="UTC"),  # daily at 6am
    work_pool_name="my-work-pool",
    tags=["production", "etl"],
)
deployment.apply()

# --- CLI equivalent ---
# prefect deploy --name production-etl
# prefect worker start --pool my-work-pool
# prefect deployment run etl-pipeline/production-etl

Error Handling & Subflows

# Error handling, retries, and subflows
from prefect import flow, task
from prefect.utilities.annotations import quote
import asyncio

@task(retries=5, retry_delay_seconds=exponential(60))
async def call_flaky_api(endpoint: str) -> dict:
    async with httpx.AsyncClient() as client:
        resp = await client.get(endpoint, timeout=10)
        resp.raise_for_status()
        return resp.json()

@flow
def process_region(region: str) -> dict:
    """Subflow — runs independently, has its own run in the UI."""
    data = call_flaky_api(f"https://api.example.com/{region}")
    return {"region": region, "count": len(data)}

@flow(name="Global ETL", on_failure=[send_alert])
def global_etl():
    # Run subflows for each region — appear as nested runs in UI
    results = [process_region(r) for r in ["us", "eu", "ap"]]
    total = sum(r["count"] for r in results)
    print(f"Total records: {total}")

def send_alert(flow, flow_run, state):
    """Called automatically on flow failure."""
    print(f"Flow {flow.name} failed: {state.message}")

Prefect vs Airflow vs Dagster

Feature Prefect Apache Airflow Dagster
Workflow definition @flow / @task decorators DAG object + operators @job / @op / @asset
Dynamic workflows Native Python Limited (workarounds) Native (partitions)
Managed cloud Prefect Cloud (free tier) Astronomer / MWAA Dagster Cloud
Learning curve Low Medium Medium–High
Data lineage Limited Plugin required First-class (Assets)
Best for Python-native teams, quick start Complex ETL, large teams Data platform / lineage

Frequently Asked Questions

What is the difference between Prefect Flows and Tasks?▼

A Flow is the top-level Python function decorated with @flow — it defines the overall pipeline, orchestrates tasks, and is the unit tracked in Prefect Cloud or the local server. A Task is a function decorated with @task — it represents a single step with its own retry logic, caching, and state tracking. Tasks run inside flows. Flows can call other flows (subflows), which appear as nested runs in the UI. In Prefect, any Python function can be a flow or task — there is no special DAG class to define.

How does Prefect compare to Apache Airflow?▼

Airflow defines workflows as DAG objects in Python files — the task graph must be static at parse time, and dynamic patterns require workarounds. Prefect workflows are plain Python functions — control flow (loops, conditionals, dynamic task mapping) is native Python. Prefect has a gentler learning curve and requires less boilerplate. Airflow has a larger ecosystem of operators and is more battle-tested at scale in data engineering teams. Prefect Cloud provides a managed control plane with no infrastructure to run. Airflow requires running a scheduler, webserver, and metadata database.

What is a Prefect Work Pool and why is it needed?▼

A Work Pool is an infrastructure abstraction that defines where and how flows run. Workers poll work pools for scheduled runs and execute them in the configured infrastructure: local processes, Docker containers, Kubernetes pods, ECS tasks, or Cloud Run jobs. Work pools decouple the scheduling decision (made by Prefect) from the execution decision (made by the worker). This allows a single Prefect Cloud account to orchestrate flows across multiple environments. Create a work pool with prefect work-pool create and start a worker with prefect worker start --pool my-pool.

How does Prefect handle retries and failures?▼

Task retries are configured on the @task decorator with retries=N and retry_delay_seconds. The delay can be a fixed number, a list of delays for each retry, or an exponential backoff. When a task fails, Prefect marks it as Failed, waits the retry delay, and re-runs it. If all retries are exhausted the task enters a Crashed state and the flow fails. Flow-level failure hooks (on_failure=[callback]) allow you to send alerts, update a status page, or trigger remediation when a flow fails. Tasks that raise exceptions report as Failed; tasks that error at the system level (OOM, segfault) report as Crashed.

What is task caching in Prefect and how should I use it?▼

Task caching allows Prefect to skip re-running a task and return the cached result if the same inputs were used within a cache expiration window. Configure it with cache_key_fn=task_input_hash (cache key is the hash of all inputs) and cache_expiration=timedelta(hours=1) on the @task decorator. This is useful for expensive API calls or data fetches in development — run the flow multiple times without re-hitting the API. In production, use caching carefully: be sure cache invalidation happens when data changes. Use a custom cache_key_fn for deterministic keys based on your business logic.

What is the difference between Prefect Cloud and self-hosted Prefect Server?▼

Prefect Cloud is the managed SaaS control plane hosted by Prefect — it provides the UI, API, scheduling, and state database with no infrastructure to operate. It has a generous free tier. Prefect Server is the same control plane running self-hosted, typically via prefect server start or Docker Compose. With self-hosted you control data residency and can run fully air-gapped, but you are responsible for operations, upgrades, and backup. In both cases, flows execute on your own infrastructure via workers — neither Prefect Cloud nor Prefect Server runs your code directly.

Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →