logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·31.10.26
← Back to news

Pre-Release of Polars 2.0

Original article
pola.rs|348 points|110 comments|by komape|Sep 3, 2026

🚀 Pre-Release of Polars 2.0

By Ritchie Vink | Published Wednesday, Sept 2, 2026

We are thrilled to announce the launch of the first Release Candidate (RC) for Polars 2.0.

Why a Major Version Bump?

The transition to 2.0 isn't just a number change. We are leveraging this major release to:

  1. Discard legacy design constraints →\rightarrow→ Eliminate architectural bottlenecks that previously hindered our progress.
  2. Optimize Defaults: Shift to settings that provide the most benefit to the widest range of users.

The most significant shift is that all LazyFrame queries will now execute on the streaming engine by default.

The Bottom Line: For the average user, this means a massive leap in performance and a significant reduction in memory overhead. We anticipate the streaming engine to be ≈5×\approx 5\times≈5× faster in aggregate.

To assist with this transition, we have provided a comprehensive [migration guide].


🌊 The Streaming Engine as Default

In Polars 2.0, calling .collect() on a LazyFrame automatically triggers the streaming engine. While this provides immense speed, it introduces a breaking change regarding data sequencing.

The Trade-off: Row Order

The streaming engine does not guarantee the preservation of row-order for specific operations, including:

  • join
  • group_by
  • unpivot

How to handle row-order requirements:

If your pipeline relies on observable row-order, you have three primary options:

  1. Query-level Opt-in: Use maintain_order=True within the specific operation.
  2. Global Fallback: Set the engine affinity for the entire process.
  3. Per-query Fallback: Specify the engine during collection.

Code Implementation Example:

import polars as pl

lf = pl.LazyFrame({"k": [2, 1, 0], "v": ["a", "b", "c"]})
other = pl.LazyFrame({"k": [0, 1, 2], "r": ["x", "y", "z"]})

# 2.0 Default: engine="auto" uses streaming. 
# Note: Row order may vary from the original lf.
result = lf.collect() 

# Option A: Maintain order for a specific join
ordered_result = (
    lf.join(other, on="k", how="left", maintain_order="left")
    .collect()
)

# Option B: Global switch back to in-memory
pl.set_engine_affinity("in-memory")

# Option C: Per-query switch
result_mem = lf.collect(engine="in-memory")

🛡️ A Stricter Polars

Polars is evolving to be more rigorous. Our philosophy is to fail fast. It is far better for a pipeline to crash immediately than to run for 20 minutes only to produce incorrect data due to a silent mismatch.

"Implicit behavior on data-mismatches should be opt-in, not a default, since those mismatches can hide bugs."

AI-Driven Development & Schema Validation

This strictness is particularly beneficial for AI agents. By utilizing collect_schema(), agents can:

  • Resolve types early.
  • Catch schema-level errors without materializing data.
  • Iterate rapidly through query structures.

Examples of Increased Strictness

1. Lossless Type-Coercion in is_in

Previously, is_in would cast different data types to a common supertype, even if that cast was lossy.

The Danger Zone: Imagine checking a user ID against a list of flagged IDs loaded from JSON (where large integers became floats).

  • User ID: 9007199254740993
  • Float64 limit: 253=90071992547409922^{53} = 9007199254740992253=9007199254740992

In 1.x, the ID would be coerced to Float64, silently rounding down and creating a false positive. In 2.0, this triggers an InvalidOperationError.

2. Strict Horizontal Concatenation

Horizontal concatenation no longer silently fills missing rows with null.

FeaturePolars 1.x (Implicit)Polars 2.0 (Strict)
Mismatched LengthsFills with nullRaises ShapeError
BehaviorSilent failure/paddingExplicit crash
SolutionN/AUse how="horizontal_extend"
# This will now raise a ShapeError in 2.0
transactions = pl.DataFrame({"day": [1, 2, 3, 4, 5], "count": [120, 98, 143, 87, 156]})
fraud_flags = pl.DataFrame({"flagged": [2, 0, 5, 1]}) # Only 4 rows

# pl.concat([transactions, fraud_flags], how="horizontal") # ❌ ERROR

3. Removal of Ambiguous Casts

We have removed several .cast() options in favor of dedicated constructors to ensure there is only one "obvious" way to parse data.

  • .cast(pl.Enum) →\rightarrow→ Use .cat.to(dtype) (for int →\rightarrow→ categorical) or .cat.physical() (for categorical →\rightarrow→ int).
  • .cast(pl.Date) →\rightarrow→ Use dedicated parsing expressions to maintain control over date formats.

🛠️ Improved Error Reporting

To ensure a smooth transition for humans and AI agents alike, we've introduced more descriptive exceptions for deprecated features:

  • polars.exceptions.AttributeRemovedError: Triggered when a removed attribute or method is accessed.
  • polars.exceptions.ArgumentRemovedError: Triggered when a removed parameter is passed to a function.

Example: If you attempt to use a removed parameter in the melt function, you will now receive a specific AttributeRemovedError rather than a generic failure.

Polars Logo (Placeholder for Polars Branding)