From Hand Tools to Horsepower: The Case for DuckDB

From Hand Tools to Horsepower: The Case for DuckDB

2025-06-09

By Paul DeSalvo

15 min read

data engineeringDuckDBPandasSQLdata processinganalyticsbest practices

We ask a lot of Pandas — more than it was ever built to handle.

It’s a memory-only tool with no persistent storage, no native SQL support, and a custom API that’s great until it isn’t. When the data’s small, it flies. But once you start pulling in larger datasets or integrating APIs at scale, Pandas starts to crack.

You get notebook crashes. Mysterious dtype errors. Merge operations that balloon memory usage. And if you need to persist state between runs? You're duct-taping together file dumps or hoping pickle saves the day.

This isn’t about dunking on Pandas — it’s doing what it was designed to do. But it was never meant to be your data pipeline.

DuckDB, on the other hand, is built for exactly this: handling large data, on your machine, with SQL, persistence, and performance baked in.

This post is about where Pandas starts to break — and why DuckDB might be the tool that gets you moving again.

The Right Tool for the Field: Pandas, DuckDB, and Spark as Farming Tools

Pandas is a hand trowel. DuckDB is a tractor. Spark is an industrial harvester.

All three can move dirt. But they’re built for different jobs — and pretending otherwise just wastes time.

Pandas: The Hand Trowel

Pandas is great when the dataset is small and the job is precise. You want to dig into a CSV, run some filters, build a quick plot — perfect. You’re close to the data, in full control, and can react fast.

But try to plow an acre with a trowel, and you’re in for a bad time. You’ll hit memory limits, type inconsistencies, and logic that worked fine yesterday will suddenly crash because today’s data is 10x larger.

This isn’t about misuse — it’s about scale. Pandas was made for garden beds, not crop fields.

DuckDB: The Tractor

DuckDB hits the sweet spot. It’s still your farm. Still local. But now you’ve got horsepower.

You can define your rows (schema), run repeatable routes (SQL), store outputs in the barn (disk), and come back tomorrow without redoing everything.

It’s not just faster — it’s smarter. You’re not reinventing your pipeline every morning. You’re building infrastructure that handles batch loads, schema evolution, and deduplication without a fuss.

It feels like cheating — but it’s just the right tool.

Spark: The Industrial Harvester

Spark is built for serious scale. Massive datasets, distributed compute, real-time ingestion. It can mow through terabytes like it’s nothing — but only if you’ve got the crew, the setup, and the budget to support it.

Spin up a Spark cluster to load 10GB of API data and you’ll spend more time managing the cluster than processing the data. It’s like hiring a combine harvester to weed your garden.

That doesn’t mean Spark is bad. It just means most of us don’t need an industrial solution to solve local problems.

Where Pandas Starts to Crack

Pandas is brilliant — until it isn’t.

It gives you fast, expressive tools for working with structured data. The API is rich, the docs are solid, and if your dataset fits in memory, it feels almost magical.

But under the hood, Pandas is fundamentally a single-node, in-memory engine. That means:

  • Every row lives in RAM.
  • There’s no built-in way to persist state across runs.
  • You’re stuck with Python-native operations — not SQL.
  • Schema consistency? You’re on your own.

So what happens when you scale?

  • You load a 3GB CSV and the kernel dies.
  • You run .merge() and suddenly you’re using 8x the memory.
  • You hit a weird dtype cast issue because one null snuck into a column.
  • You waste an hour debugging a join that would be one line in SQL.

This isn’t a knock on Pandas. It’s just not the right tool once your data stops being “small.” And it was never meant to be.

If you’re spending more time wrangling the environment than analyzing the data, that’s the sign. Pandas isn’t broken — it’s just out of its depth.

Why DuckDB Hits the Sweet Spot

DuckDB lives in the middle — and that’s what makes it powerful.

It runs locally like Pandas, but thinks like a database. You get fast, analytical performance without the complexity of clusters, and SQL instead of custom Python transformations.

Here’s what makes DuckDB different:

  • It’s SQL-native. You’re not learning a new API — you're just writing SQL. If you know how to query a warehouse, you already know how to use DuckDB.
  • It supports persistent storage. No more reloading data or writing fragile intermediate files. DuckDB stores everything in a .duckdb file that just works.
  • It plays nice with Pandas. You can query huge datasets in DuckDB, and when you actually need to bring something into a DataFrame, you just .df() it.
  • It handles memory better. With lazy evaluation and vectorized execution, you’re not loading every intermediate result into RAM.
  • It’s blazing fast. For analytics workloads on a single machine, DuckDB often outperforms Pandas — and even rivals Spark for smaller jobs.

DuckDB gives you the performance of a warehouse, the simplicity of a library, and the ergonomics of SQL — all without leaving your laptop.

It’s not a Spark replacement. It’s a smarter alternative to shoehorning Pandas into problems it was never built to solve.

Real-World Workflows Where DuckDB Just Works

This isn’t theoretical. Here’s where DuckDB has become my go-to — especially when Pandas starts to fight back.

API Integrations

APIs love to send messy JSON. Sometimes the schema shifts. Sometimes a field is missing. Sometimes you get 100k rows and need to dedupe against last week’s pull.

  • With Pandas: You’re juggling json_normalize, fixing dtypes manually, and hoping your joins don’t eat 8GB of RAM.
  • With DuckDB: You write the schema once, store batches incrementally, and handle updates with INSERT OR REPLACE. Done.

DuckDB’s ability to persist data locally means you don’t need to reload everything every time. Just append new records and move on.

Schema Consistency

If you’ve ever had a Pandas merge blow up because of one None value getting miscast, you know the pain of inconsistent types.

DuckDB lets you define your schema up front — and enforces it. That means fewer surprises, fewer workarounds, and a clean pipeline you can trust.

Ad Hoc Analysis on Large Files

You get handed a 4GB CSV and a vague question: “Can you check something?”

  • With Pandas: You chunk it, write temporary CSVs, maybe downcast dtypes to save RAM.
  • With DuckDB: You run a SQL query directly against the file — no need to load it into memory at all.

It’s like having a mini data warehouse that speaks fluent CSV, Parquet, JSON, and your local disk.

Incremental Data Loads

Whether it’s log files, exported tables, or batch API pulls — real pipelines are often incremental.

DuckDB lets you build pipelines where each batch:

  • Gets validated
  • Gets merged into a persistent store
  • Can be queried or exported immediately

No glue scripts. No weird intermediate formats. Just SQL, files, and results.

No worries at all — here’s just the last section (“DuckDB vs. Pandas: Same Job, Different Tool”) fully written in Markdown:

markdown
Copy
Edit

DuckDB vs. Pandas: Same Job, Different Tool

Still not sure how different it really is? Let’s walk through three common data tasks — first in Pandas, then in DuckDB — so you can see the tradeoffs for yourself.

Task 1: Import a CSV

With Pandas:
import pandas as pd df = pd.read_csv("data.csv") Easy — but you’ve just loaded everything into memory.
With DuckDB:
import duckdb con = duckdb.connect() df = con.execute("SELECT * FROM 'data.csv'").df() Reads directly from disk with SQL. You don’t even need to materialize the whole thing unless you want to.

Task 2: Filter + Add Column

With Pandas:

Copy Edit df = df[df["score"] > 80] df["category"] = df["score"].apply(lambda x: "high" if x > 90 else "medium") Custom logic, row-by-row operation, must fit in memory.
With DuckDB:
SELECT *, CASE WHEN score > 90 THEN 'high' ELSE 'medium' END AS category FROM 'data.csv' WHERE score > 80;

One SQL query. No extra memory usage. Lazy evaluation all the way down.

Task 3: Join Two Datasets

With Pandas:
df1 = pd.read_csv("left.csv") df2 = pd.read_csv("right.csv") df_merged = pd.merge(df1, df2, on="user_id", how="inner") Straightforward — but risky if both files are large.
With DuckDB:
SELECT * FROM 'left.csv' l JOIN 'right.csv' r ON l.user_id = r.user_id;

Joins are handled at the SQL level — more efficient and disk-backed by default.

Why This Matters

With Pandas, every operation loads data into memory, and you’re writing Python logic for even simple transforms. With DuckDB, you keep things declarative, SQL-based, and disk-friendly — which means you can scale further without rewriting your pipeline.

And when you do need a DataFrame?

df = con.execute("SELECT * FROM my_table").df()

You can drop into Pandas at the very end — not the beginning.

When to Use What

Let’s challenge the default: Do you really need Pandas at all?

In most cases, DuckDB handles the same workflows — faster, more reliably, and with better long-term structure. Pandas isn’t bad, but it’s no longer the obvious first choice.

ToolUse It When...Skip It When...
PandasYou need to pass a DataFrame to a library that requires it (e.g. charting).You’re doing real data processing — filtering, joining, transforming.
DuckDBYou want fast, SQL-based workflows with persistent storage.You’re doing distributed compute or real-time streaming.
SparkYou’re operating at true scale — TBs+ across a cluster.You’re prototyping or working locally on a laptop.

Bottom Line:

If you're reaching for Pandas out of habit — don't.

DuckDB can read your CSVs, clean your API data, run your joins, and write your output. All with SQL. All without blowing up your memory.

Use Pandas as a formatting layer — not a processing engine.

Final Thoughts: Ditch the Duct Tape

Most data teams don’t need bigger tools — they need better defaults.

Pandas was a great starting point. But if you’re building anything that needs to scale, persist, or repeat — it’s time to move on. DuckDB gives you the performance of a database with the simplicity of Python and the power of SQL.

It’s not a replacement for everything. It’s just better for most things.

So next time your notebook crashes or your pipeline feels fragile, ask yourself:

“What if I didn’t use Pandas at all?”

Try DuckDB. Query your files. Save your state. Think in SQL.
It’s not magic. It’s just the right tool.