Polars 2.0 was released on 6 October 2026, and if you are a pandas user who keeps meaning to try it, this is a reasonable moment. The release makes the streaming engine the default for lazy queries, adds initial spill-to-disk support for data that does not fit in memory, and tightens a lot of type rules. Two of those changes catch people who move from pandas without reading the notes. Here is what they are, what the headline benchmarks do and do not tell you, and a simple way to decide whether switching is worth it for your work.
The mental shift from pandas
The biggest difference is not syntax. It is when the work happens.
pandas is like cooking each step as you read the recipe: filter, then group, then sort, each one done immediately. Polars' lazy API is like reading the whole recipe first, then cooking it in the most efficient order. Nothing runs until you call collect().
The same summary in both. First, pandas:
import pandas as pd
df = pd.read_csv("sales.csv")
result = (
df[df["amount"] > 0]
.groupby("region", as_index=False)["amount"].sum()
.rename(columns={"amount": "total"})
.sort_values("total", ascending=False)
)
And Polars:
import polars as pl
result = (
pl.scan_csv("sales.csv")
.filter(pl.col("amount") > 0)
.group_by("region")
.agg(pl.col("amount").sum().alias("total"))
.sort("total", descending=True)
.collect()
)
Because Polars sees the whole query, it can skip columns you never use and push the filter down into the file read. That is where much of its speed comes from.
Change 1: row order is no longer guaranteed
In 2.0, collect() on a lazy query uses the streaming engine by default. The official upgrade guide warns: "The streaming engine does not guarantee row order for operations that don't require it." That includes group_by, joins and unpivot.
This is a classic analytical mistake waiting to happen. Suppose you group orders by region and then take head(3), assuming the rows come back in some meaningful order. In pandas, groupby sorts keys by default, so the habit feels safe. In Polars 2.0, the top three rows can differ from run to run. Your "top regions" chart is now random, and nothing errors.
The fix is to say what you mean:
top = (
orders.group_by("region")
.agg(pl.len().alias("n_orders"))
.sort("n_orders", descending=True) # never rely on implicit order
.head(3)
.collect()
)
And for joins where you need the left table's order kept:
joined = orders.join(customers, on="customer_id", how="left",
maintain_order="left")
If you need time to audit old code, the guide shows how to restore the previous engine, per query with collect(engine="in-memory") or process-wide with pl.Config.set_engine_affinity("in-memory"). Treat that as a bridge, not a fix.
Change 2: stricter types, and some renamed functions
Polars 2.0 prefers to raise an error over quietly guessing. A few examples from the guide that matter to analysts:
is_in()only allows lossless casts, sopl.Series([1]).is_in(pl.Series([1.99]))now raises. Cast explicitly.Combining a signed integer column with a
UInt64column now producesInt128instead ofFloat64. The values change with no error, so check any arithmetic across those types.Horizontal
pl.concatrequires frames of equal height.For headerless CSVs, column names now start at
column_0instead ofcolumn_1, which will quietly break code that selects by those names.
And the renames you will hit first: melt() is gone, use unpivot(); with_row_count() becomes with_row_index(); join(how="outer") becomes how="full"; and group_by(...).count() becomes len().
Stricter is better for analysis. A silent cast from integer to float is how a customer ID like 9007199254740993 turns into a different number. But it means old snippets from Stack Overflow will fail more often.
What the benchmarks show, and what they don't
The release post claims Polars leads DataFusion and DuckDB on TPC-H and TPC-DS benchmarks. Before quoting that in a team meeting, read the conditions:
The tests ran on cloud machines with 16 and 192 virtual CPUs, at large scale factors.
Each query ran five times "in a hot setting", keeping the best time.
The comparison used DuckDB 1.5.6 and a 2.0 alpha, not the DuckDB 2.0 release that shipped on 21 October.
A footnote says the results are not comparable to published TPC results, and the post itself notes Polars has overhead at 192 threads that hurts small queries.
None of this makes the numbers wrong. It makes them about a different situation from a 2 GB CSV on your laptop. The only benchmark that matters for your decision is your own workload.
So, should you switch?
Your pandas code works and the data fits in memory: no rush. Rewriting working analysis for speed you do not need is a poor use of a week.
You hit memory limits or wait minutes for each run: try Polars on that one pipeline. The new spill-to-disk support is an initial version covering sort, window functions and many expressions; out-of-core joins and group-bys are planned, not done.
You are starting a new pipeline: starting in Polars' lazy API is a good default now. Moving to pandas at the end is one call,
to_pandas(), for plotting or a library that needs it.
A one-afternoon trial
Install it in a separate environment (polars 2.0.0 on PyPI needs Python 3.10 or later). Pick your slowest notebook. Rewrite only the data-loading and aggregation steps in Polars, add an explicit sort after every group_by, and compare both the runtime and the output, row by row. If the numbers match and the time drops a lot, you have your answer. If they do not match, you have just found the row-order or type change before it reached a dashboard.



