- Posted on
- admin
- No Comments
DuckDB Tutorial for Beginners
Spinning up a Postgres server, configuring a connection, and loading a CSV just to run a handful of SQL queries against it is a lot of ceremony for what’s often a five-minute analysis task. DuckDB exists specifically to remove that ceremony. It’s a database that lives inside your own process, starts in milliseconds, and can query a CSV, a Parquet file, or a Pandas DataFrame sitting right where it already is, no server, no configuration, no separate process to manage.
If you’ve read our guide to MotherDuck, you’ve already seen DuckDB mentioned as the engine underneath it. This tutorial is the dedicated beginner’s walkthrough: what DuckDB actually is, why its architecture makes it fast, the SQL quality-of-life features that make it pleasant to use, and a hands-on quickstart covering local files, remote Parquet over S3, and Python DataFrame integration.
What Is DuckDB?
(cite index=”50-1″>DuckDB can be described in either of two ways: an open-source relational database management system that supports SQL, or an in-process SQL OLAP (online analytical processing) database management system</cite>. (cite index=”52-1″>It was built at CWI, the Dutch national research institute for mathematics and computer science, by researchers Hannes Mühleisen and Mark Raasveldt, with the first open-source version released in 2019</cite>, and it’s licensed under the permissive MIT license.
The “in-process” part is the single most important thing to understand before anything else. Rather than running as a separate server you connect to over a network the way Postgres or MySQL do, DuckDB runs directly inside your own application, script, or notebook, in the same process as your code. That’s exactly the design SQLite popularized for transactional workloads, DuckDB brings that same embedding model to analytical, columnar workloads instead, which is why it’s often described as “SQLite for analytics.”
How DuckDB’s Architecture Works
DuckDB’s speed isn’t an accident of implementation, it’s a direct consequence of a handful of deliberate architectural choices, all aimed squarely at the kind of scan-heavy, aggregation-heavy queries analytics workloads actually run.
Columnar Storage
(cite index=”40-1″>DuckDB uses a columnar storage engine, purpose-built for OLAP, storing data column by column rather than row by row. Columnar storage lets DuckDB read only the columns a query actually needs, reducing I/O overhead</cite> dramatically compared to a row-oriented database that has to read entire rows even when a query only touches two or three columns out of twenty.
Vectorized Execution
(cite index=”44-1″>DuckDB achieves exceptional performance through vectorized execution optimized for CPU cache efficiency, processing batches of rows, called vectors, at once rather than one row at a time</cite>. (cite index=”40-1″>This is a sharp contrast to a row-at-a-time, iterator-based execution model</cite>, and it matters because modern CPUs are dramatically more efficient when they can apply the same operation to a batch of values in a tight, cache-friendly loop, often using SIMD instructions, rather than branching and dispatching separately for every single row.
Zone Maps and Automatic Parallelism
(cite index=”44-1″>DuckDB also uses zone maps for selective scanning, keeping lightweight min/max statistics per column chunk so it can skip entire chunks that can’t possibly match a query’s filter, along with automatic parallelization across all available CPU cores</cite>, meaning a single query on your laptop can transparently use every core available without any special configuration on your part.
Storage and Concurrency Model
DuckDB persists data to a single database file on disk, rather than a directory of many small files, which is part of why moving or backing up a DuckDB database is as simple as copying one file. (cite index=”47-1″>DuckDB provides full ACID transactions, a write-ahead log, and MVCC, but write concurrency is centered around a single writer process</cite>.
That last detail is worth understanding clearly, since it’s the most common point of confusion for anyone coming from a client-server database. (cite index=”41-1″>DuckDB supports concurrency within a single process: multiple writer threads can operate concurrently using a combination of MVCC and optimistic concurrency control, which kicks in specifically when two threads attempt to edit the same row at the same time</cite>. (cite index=”45-1″>Writing to DuckDB’s native database format from multiple separate processes at once is not currently supported</cite>, and (cite index=”42-1″>DuckDB handles concurrent access using file locks, which is worth extra caution if you’re accessing a database file from a shared or network-attached location</cite>. In practice: many threads within one Python script or one application process can read and write concurrently just fine, but you can’t have two separate programs both writing to the same .duckdb file at the same time the way you could with a proper client-server database.
Key Features
Friendly SQL. DuckDB extends standard SQL with genuine quality-of-life improvements rather than inventing a new dialect. (cite index=”49-1″>A FROM tbl query with no SELECT clause at all is valid and selects every column, the SELECT * EXCLUDE syntax lets you select everything except specific named columns, and GROUP BY ALL automatically infers the group-by columns from whatever non-aggregated columns appear in your SELECT clause</cite>. (cite index=”49-1″>Column aliases can be reused directly within the same SELECT, so SELECT i + 1 AS j, j + 2 AS k FROM range(0, 3) t(i) works exactly as you’d hope, referencing j again before the query is even finished</cite>, something most SQL dialects flatly reject.
Direct querying of files. DuckDB can run SQL directly against a CSV, JSON, or Parquet file sitting on disk, or sitting in S3, without a separate import step first. You get a real SQL engine pointed straight at your data where it already lives.
Seamless DataFrame integration. (cite index=”34-1″>Pandas DataFrames stored in local Python variables can be queried as if they were regular DuckDB tables, through replacement scans that transparently swap in a table function reading the DataFrame whenever your SQL references its variable name</cite>. The same integration extends to Polars and Apache Arrow.
A broad extension ecosystem. (cite index=”39-1″>The widely used httpfs extension enables reading and writing remote files over HTTPS and S3</cite>, and (cite index=”37-1″>DuckDB natively supports a wide range of formats including Parquet, CSV, JSON, Iceberg, Delta Lake, Excel, Avro, and Arrow</cite>, alongside scanner extensions for querying MySQL, PostgreSQL, and SQLite databases directly.
Native clients everywhere. (cite index=”37-1″>DuckDB ships official clients for the CLI, Python, Go, Java, Node.js, C, C++, R, Rust, and ODBC</cite>, plus a WebAssembly build that runs the identical engine directly inside a browser tab.
The Extension Ecosystem in More Detail
Extensions are how DuckDB stays small and fast by default while still covering a huge range of use cases, and it’s worth understanding the install-then-load pattern before you hit it in the quickstart below. (cite index=”39-1″>To install an extension, use the INSTALL command followed by the extension’s name. Once installed, DuckDB saves it to the $HOME/.duckdb/ directory by default, and you then activate it in your current session with LOAD</cite>.
A few extensions are worth knowing about by name, since they cover the situations beginners run into most often:
- httpfs enables reading and writing files directly over HTTPS and S3-compatible object storage, the extension used in the quickstart’s remote Parquet example below.
- parquet and json add first-class reading and writing support for those formats, and are built in by default in recent versions.
- iceberg and delta let DuckDB read tables stored in those open table formats directly, the same formats covered in our Iceberg and Delta Lake guides.
- spatial adds geospatial types and functions for working with geographic data.
- mysql, postgres, and sqlite scanner extensions let DuckDB query those databases’ tables directly, useful for one-off analytical queries against an operational database without standing up a separate ETL pipeline first.
- motherduck is the extension that connects a local DuckDB session to a MotherDuck cloud account, enabling the Dual Execution model covered in our MotherDuck guide.
Because extensions are loaded on demand rather than bundled into one monolithic binary, a basic DuckDB install stays small and fast to start, while still being able to grow into handling cloud storage, geospatial data, or an entirely different database’s tables the moment you actually need that capability.
Performance Tips Worth Knowing Early
A few habits make a real difference once you’re working with anything beyond toy-sized data:
- Prefer Parquet over CSV wherever you control the format. Parquet’s columnar, compressed layout lets DuckDB skip irrelevant columns and row groups entirely; CSV offers no such shortcuts and has to be parsed row by row regardless of which columns a query actually needs.
- Filter early in cloud-storage queries. When querying remote Parquet via
httpfs, pushing filters into the same query (rather than pulling everything back and filtering afterward) lets DuckDB’s zone maps skip entire files or row groups before any data transfer happens. - Use
EXPLAINbefore optimizing blindly. RunningEXPLAINin front of a slow query shows DuckDB’s actual execution plan, which columns and files it expects to scan, letting you confirm whether a filter or projection is actually being pushed down the way you’d expect. - Let DuckDB use all your cores. Parallelism is automatic by default, but if you’re running inside a container or CI environment with a restricted CPU count, double-check that DuckDB can actually see the cores you expect it to use.
Hands-On Quickstart
The fastest way to get a feel for DuckDB is to actually run a few queries, starting local and working up to querying data that lives entirely in the cloud.
Step 1: Install DuckDB
For the CLI, on Linux or macOS:
curl https://install.duckdb.org | sh
For Python:
pip install duckdb
Step 2: Run Your First Query
Launch the CLI and run a trivial query to confirm it’s working:
duckdb
SELECT 42 AS hello;
Step 3: Create a Persistent Database and a Table
By default, the CLI opens an in-memory database that disappears when you close it. Point it at a file path instead to persist data:
duckdb my_analysis.duckdb
CREATE TABLE orders (
id INTEGER,
customer VARCHAR,
amount DOUBLE,
order_date DATE
);
INSERT INTO orders VALUES
(1, 'alice', 49.99, '2026-01-15'),
(2, 'bob', 129.50, '2026-01-20'),
(3, 'alice', 19.99, '2026-02-03');
Step 4: Query a CSV File Directly, No Import Needed
Assuming you have a local CSV file, DuckDB can query it in place:
SELECT * FROM 'orders_export.csv' WHERE amount > 50;
No CREATE TABLE, no loading step, DuckDB infers the schema and reads the file directly as part of running the query.
Step 5: Try Friendly SQL Features
-- FROM-first syntax, no SELECT needed
FROM orders;
-- GROUP BY ALL instead of listing group columns manually
SELECT customer, SUM(amount) AS total_spent
FROM orders
GROUP BY ALL;
-- SELECT * EXCLUDE to drop a column without naming the rest
SELECT * EXCLUDE (order_date) FROM orders;
Step 6: Query a Remote Parquet File Over S3
Install and load the httpfs extension, then query a Parquet file that lives entirely in cloud storage, without downloading it first:
INSTALL httpfs;
LOAD httpfs;
SELECT COUNT(*) FROM read_parquet('s3://my-bucket/orders/*.parquet');
That single query reads only the row groups and columns it actually needs from remote storage, thanks to the same columnar, zone-map-driven pruning covered in the architecture section above.
Step 7: Query a Pandas DataFrame From Python
import duckdb
import pandas as pd
df = pd.DataFrame({
"customer": ["alice", "bob", "alice"],
"amount": [49.99, 129.50, 19.99]
})
result = duckdb.sql("SELECT customer, SUM(amount) AS total FROM df GROUP BY ALL").df()
print(result)
Notice df is referenced directly in the SQL, no explicit registration step required, DuckDB’s replacement scan mechanism finds the Python variable automatically.
Step 8: Export Results to Parquet
COPY (
SELECT customer, SUM(amount) AS total_spent
FROM orders
GROUP BY ALL
) TO 'customer_totals.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);
Common Beginner Mistakes to Avoid
- Expecting multi-process concurrent writes to work. DuckDB’s native file format supports one writer process at a time. If you need multiple separate applications writing to the same data concurrently, that’s a client-server database’s job, not DuckDB’s.
- Loading everything into memory out of habit. A lot of tutorials for other tools start with “import your CSV into a table first.” With DuckDB, querying the file directly with
read_csvorread_parquetis usually both simpler and faster, skip the import step unless you actually need the data to persist. - Forgetting to install AND load an extension.
INSTALL httpfsdownloads the extension once;LOAD httpfsis still needed in each new session to actually activate it. - Treating DuckDB as a drop-in replacement for a production client-server database. It’s an excellent embedded analytical engine, not a multi-user transactional backend for a web application with many concurrent writers.
- Ignoring Parquet as an output format. Writing query results back out as CSV when Parquet would be smaller, faster to re-read, and columnar-compressed is a missed opportunity, especially for anything you’ll query again later.
DuckDB vs. SQLite vs. Pandas, Briefly
It’s worth placing DuckDB against the two tools beginners most often compare it to. (cite index=”40-1″>Against SQLite specifically, the core difference is storage model and execution: SQLite is row-based with iterator-style, single-threaded query execution, while DuckDB is columnar with vectorized, multi-core parallel execution</cite>, which is exactly why SQLite remains the better choice for transactional, row-at-a-time application data, while DuckDB is built for scanning and aggregating large analytical datasets.
Against Pandas, the comparison is less about raw capability and more about interface and scale. DuckDB lets you express transformations as SQL rather than chained DataFrame method calls, runs on genuinely parallel, out-of-core execution rather than holding everything in memory as a single Pandas object, and, as shown in the quickstart above, can query a Pandas DataFrame directly, making it a natural complement to Pandas rather than a strict replacement.
Who Should Use DuckDB?
DuckDB tends to be an excellent fit for:
- Data analysts and data scientists doing local exploration, who want real SQL against CSVs and Parquet files without touching a server.
- Engineers building ETL or data pipeline steps that need fast, embedded transformation logic without the overhead of spinning up and tearing down a separate database for each job.
- Anyone already using Pandas or Polars who wants SQL’s expressiveness for complex joins and aggregations without leaving their existing Python workflow.
- Teams building applications that need a fast, embedded analytical engine inside their own software, rather than a shared service other applications also depend on.
- Anyone reading data that already lives in Parquet, Iceberg, or Delta Lake format and wants a lightweight, fast query layer on top without standing up a distributed engine.
It’s a weaker fit when you need many separate applications or services writing to the same database concurrently, a workload DuckDB’s single-writer-process design doesn’t target, or when your working dataset is dramatically larger than the memory and disk available on a single machine, at which point a distributed engine becomes the more appropriate tool.
Frequently Asked Questions
Is DuckDB a server I need to install and run? No. DuckDB is in-process, meaning it runs directly inside your own application or script rather than as a separate server you connect to over a network. There’s no server to start, stop, or manage.
Can multiple people write to the same DuckDB database at once? Not across separate processes. DuckDB supports multiple writer threads concurrently within a single process using MVCC and optimistic concurrency control, but writing to its native database format from more than one process at the same time isn’t currently supported.
Do I need to import my CSV or Parquet file before querying it? No, DuckDB can run SQL directly against a CSV, JSON, or Parquet file in place, locally or in cloud storage via the httpfs extension, without a separate loading step.
Is DuckDB free and open source? Yes. DuckDB is licensed under the permissive MIT license and is fully open source.
What’s the difference between DuckDB and SQLite? SQLite uses row-based storage with single-threaded, iterator-style query execution, built for transactional application data. DuckDB uses columnar storage with vectorized, multi-core parallel execution, built for analytical queries that scan and aggregate large amounts of data.
Can DuckDB query data stored in Iceberg or Delta Lake tables? Yes, DuckDB has native support for reading both formats, which is part of what makes it a natural lightweight query engine on top of an existing data lakehouse without requiring a heavier distributed engine.
Does DuckDB replace Pandas? Not exactly, the two complement each other well. DuckDB can query a Pandas DataFrame directly via SQL, and many workflows use DuckDB for heavier joins and aggregations while keeping Pandas for other parts of an analysis.
Is DuckDB suitable for production applications? It’s well suited as an embedded analytical engine inside a production application, a reporting pipeline, or a data processing job. It’s not designed as a shared, multi-process, multi-writer backend the way a client-server database like Postgres is, so the right fit depends on which of those two roles your application actually needs.
Final Thoughts
DuckDB’s appeal comes down to removing friction that most people had simply learned to accept as normal: spinning up a server, configuring a connection, importing data before you can query it. Its columnar, vectorized architecture isn’t just an implementation detail, it’s the reason a query against a multi-gigabyte Parquet file on your own laptop can come back in a fraction of a second, and friendly SQL features like GROUP BY ALL and reusable column aliases make the day-to-day experience of writing that SQL noticeably less tedious too.
Once a single machine and a single writer process stop being enough, that’s exactly the point where MotherDuck picks up, extending the same engine and the same SQL dialect into the cloud without asking you to learn a different system. For the authoritative, continuously updated reference on every SQL feature, extension, and client covered here, DuckDB’s official documentation is the best place to go deeper.
For more breakdowns of open-source data infrastructure and analytics tooling like this one, keep exploring the guides on CourseDrill.
Popular Courses
