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.
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.
Related Products
on GumroadDeveloper Productivity Bundle
Stop rebuilding the same setup every project. 51 VSCode snippets, 40 Git aliases, 5 Docker Compose stacks, 5 GitHub Actions workflows, 5 README templates, and a dotfiles kit. Set up a professional dev environment in under an hour.
Buy on Gumroad →AI Prompt Engineering Toolkit
58 structured prompt templates for Claude, ChatGPT, and Gemini — organized by use case. Code Review, Debugging, Documentation, Architecture, and 10 model-specific tips. Stop writing prompts from scratch.
Buy on Gumroad →Developer Cheat Sheet Bundle
10 print-ready cheat sheets for the tools developers use every day: Git, Docker, Linux CLI, SQL, Regex, HTTP Status codes, VS Code shortcuts, Bash scripting, jq/JSON, and Markdown.
Buy on Gumroad →You Might Also Need
GitHub Actions Workflow Generator
Generate GitHub Actions YAML workflows instantly. Choose triggers (push, PR, schedule), runtime (Node.js, Python, Go, Rust), and jobs (test, lint, build, Docker, GitHub Pages). Copy-ready .github/work
Open free tool →
Regex Tester Pro
Free Regex Tester Pro with real-time match highlighting. Test regular expressions with live visual feedback. Supports JS, Python, and Ruby flavors, all flags, capture groups, and 6 built-in presets. R
Open free tool →
Dockerfile Generator
Interactive wizard to generate production-ready Dockerfiles for Node.js, Python, Go, Java, and Rust. Multi-stage builds, port config, custom layers.
Open free tool →
More Free Tools
Related Articles
Apache Airflow vs Prefect: Workflow Orchestration Comparison 2026
Apache Airflow vs Prefect 2026: DAG vs flow syntax, scheduling, dynamic workflows, observability, learning curve, cloud offerings, and which to choose for your data team.
Apache Kafka for Real-Time Data Pipelines: Tutorial 2026
Apache Kafka tutorial 2026: topics, partitions, consumer groups, producers/consumers in Python, Schema Registry, Kafka Connect, exactly-once semantics, and production best practices.
ETL vs ELT: Modern Data Pipeline Architecture Guide 2026
ETL vs ELT data pipeline comparison 2026: traditional ETL limitations, cloud ELT advantages, dbt for transformation, tools comparison (Fivetran/Airbyte/dbt), and when to use each approach.
Temporal vs Inngest vs Trigger.dev: Workflow Orchestration Comparison 2026
A production-focused comparison of Temporal, Inngest, and Trigger.dev for durable workflow orchestration in 2026 — covering execution models, developer experience, pricing, and when to use each.
Authentication Security Tools 2026: Modern Auth Patterns, MFA, Passwordless, and OAuth2
Complete guide to authentication security tools in 2026. Covers Auth0, Clerk, NextAuth.js v5, JWT security, Passkeys/WebAuthn, TOTP authenticator apps, OAuth2/OIDC, session management, and building secure auth systems with Node.js and Python.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$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.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →