Build3 publishers3 min readPublished
The release candidate keeps collect's signature and changes what its default resolves to. Group_by, joins and unpivot can now return rows in a different order, and explain() still prints the plan that was not run.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The default argument in `collect()` is still `engine="auto"`. What changed is what `auto` resolves to: the streaming engine for lazy queries, where it used to be the in-memory engine [1]. The call site itself is unchanged, and no error gets raised. The pre-2.0 row order for `group_by`, `join` and `unpivot` was never a documented contract, and the streaming engine does not offer one either [5]. That governs how you fix it. The guide's own `unpivot` example comes back grouped per index key rather than per variable, and the guide then says the order shown is not guaranteed, only that it is no longer the pre-2.0 order [7]. Re-recording a snapshot test against the new output does not pin the order down. The two supported fixes are an explicit `.sort(pl.all())` or `maintain_order` where the operation supports it, as in `join(..., maintain_order="left")` [8].
Some of the blast radius is narrower than it looks. `sink_*` was already dispatched to the streaming engine regardless of `engine`, so file-writing pipelines have had these semantics all along [3]. Eager `DataFrame` operations still resolve `auto` to in-memory internally [2]. The wider part is SQL: `pl.sql(..., eager=True)` and `SQLContext.execute(eager=True)` used to collect through the in-memory engine and now go through `LazyFrame.collect()`, so a query string with no engine argument anywhere in sight picks up the new default [9].
Column order breaks in a second, unrelated place. `pl.read_csv` is now dispatched to `pl.scan_csv(...).collect()`, gaining six scan parameters including `with_column_names` and `credential_provider`, and losing `n_threads`, `batch_size`, `sample_size` and `rechunk` [10]. Its behaviour now matches `scan_csv` exactly, so `columns=[2, 1, 3]` returns `['c', 'b', 'd']` where it used to return the sorted `['b', 'c', 'd']`, and a `schema_overrides` list must cover every column in the file or raise `SchemaError` [11]. That failure now surfaces at the boundary rather than downstream.
The strictness work is the part of this release I would ship unchanged. Polars' example is a flagged-account check: `flagged_ids` arrives from JSON as `Float64` holding 9007199254740992.0, the user ID is the `Int64` 9007199254740993, and pre-2.0 coercion to the common supertype rounded the ID down and returned a false positive [12]. The ID sits exactly one above 2^53, the largest integer float64 represents exactly [13]. In 2.0 that raises `InvalidOperationError` [12]. Horizontal `concat` now checks lengths rather than filling the short frame with nulls [14]. Both changes convert a silent wrong answer into a stack trace, which is the trade the row-order default makes in the opposite direction.
Polars expects the streaming engine to be easily 5x faster in aggregate [15]. That is a number about their aggregate. For it to transfer, your work has to be the kind that batches, which is what the streaming engine does instead of processing all data at once [16]: a working set at or past memory, or scans that stream, or joins you are willing to leave unordered. Every `maintain_order` you add pays some of it back, and The New Stack notes the speed cost without quantifying it [17].
So the sequence I would use is affinity first. Set `POLARS_ENGINE_AFFINITY=in-memory`, absorb the 2.0 API changes, get green, then move queries onto streaming one at a time as each order assumption is removed or made explicit [18]. That turns one upgrade into two changes with separate blast radii. The announcement says the team hopes 2.0 is a boring experience [19]; the red danger box in the migration guide is doing a fair amount of work to keep that promise [4].</body_markdown> </invoke>
Ranked by verification strength, evidence, and original report placement.
LazyFrame.collect and collect_async still default to engine="auto", but "auto" now resolves to the streaming engine for lazy queries; it used to resolve to the in-memory engine.
Eager DataFrame operations are unaffected and continue to resolve "auto" to the in-memory engine internally.
The streaming engine does not guarantee row order for operations that do not require it, the guide listing unpivot, group_by and joins.
The guide says joins are affected the same way as unpivot and are easy to miss, since nothing about the query looks order-sensitive.
The guide's remedies are to sort explicitly, for example .collect().sort(pl.all()), or to pass maintain_order where the operation supports it, for example join(..., maintain_order="left").
The 2.0-rc upgrade guide marks the change with a "Danger" box stating: "This change may silently impact the results of your pipelines."
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Documented to the keystroke, sourced almost entirely to Polars
Almost every specific in this story can be checked by pasting it into a REPL: the guide prints the old and new unpivot frames, the exact SchemaError text, and the TypeError for read_ipc's memory_map. What it cannot offer is independent verification — two of the three accounts are the project's own, The New Stack ran nothing, and the guide itself declines to promise the row order it displays.
One release candidate, no upgraded pipelines on record
What exists is a release candidate and its migration guide, both dated the same day in our coverage. Nobody quoted has upgraded a production pipeline, hit the reordering, or measured the new default, so the breakage remains documented rather than observed. Polars puts the final 2.0 weeks out.
Performance claim runs ahead of the evidence; the risk does not
"Easily 5x faster in aggregate" carries no query set, no hardware and no numbers, and it still became The New Stack's headline. The breaking-change side is the opposite case: a red Danger box, a join example the project admits is easy to miss, and three documented ways to switch back. That candour is what keeps the gap modest rather than wide.
Polars wrote two of the three accounts
The project is both the subject and the main author here, and it wants 2.0 adopted before the final release — which shows in a blog post that leads with 5x and skips the reader API changes entirely. Cutting the other way, the same project documents its own regression risk in detail, including the read_ipc memory_map removal that flatters nobody. The New Stack has no stake beyond the traffic.
Solid on syntax, thin on consequence
Parameter names, error types, version numbers and code paths are documented well enough to act on. Everything about scale is missing: how much faster the streaming engine actually is, what sorting or maintain_order costs, and how many pipelines quietly depend on the order Polars no longer guarantees.
build
Polars 2.0 routes every lazy collect() through the streaming engine by default1 publisher
build
CSPM ends up as the intake queue for agent-provisioned infrastructure1 publisher
build
Testing a skill means running the scenario again on the next model version1 publisher
build
A refactoring benchmark stops the best agent at 41.2%, and the tests are the story1 publisher
Publishers with included, body-backed reporting in this cluster.
1 article · September 7, 2026
1 article · September 7, 2026
1 article · September 6, 2026