Lazy mode is how Polars gets its biggest speed and memory wins, and it is the recommended way to write pipelines.
In eager mode every method runs immediately. In lazy mode methods only build a query plan, and nothing happens until you call .collect(). Get a LazyFrame from pl.scan_parquet(...), pl.scan_csv(...), pl.scan_ndjson(...) or df.lazy().
Because Polars sees the whole query before running it, it can optimize: read only the columns and row groups the query needs, push filters down into the file reader, drop unused computations, and run steps in parallel. Code reads the same as eager code; the only difference is where the query starts and the collect() at the end.
q = (pl.scan_parquet("events/*.parquet")
.filter(pl.col("country") == "NO")
.group_by("day").agg(pl.len()))
df = q.collect()In Polars 2, collect() uses the streaming engine by default: it processes data in batches across all cores, so it can handle inputs bigger than RAM. A consequence is that row order after joins, group-bys and unpivots is not guaranteed; sort explicitly when it matters. collect(engine="in-memory") selects the older engine if you need it.
lf.collect_schema() returns the result schema without running anything, and lf.head(5).collect() is the quick way to peek. Mixing is fine: collect, do something eager, then .lazy() again.
Example
import polars as pl
pl.DataFrame({
"day": ["mon", "mon", "tue", "tue", "tue"],
"country": ["NO", "SE", "NO", "NO", "SE"],
"ms": [120, 80, 95, 130, 70],
}).write_parquet("events.parquet")
q = (
pl.scan_parquet("events.parquet") # nothing is read yet
.filter(pl.col("country") == "NO")
.group_by("day")
.agg(n=pl.len(), avg_ms=pl.col("ms").mean())
.sort("day")
)
print(type(q).__name__) # LazyFrame
print(q.collect_schema()) # Schema({'day': String, 'n': UInt32, 'avg_ms': Float64})
print(q.collect().rows()) # [('mon', 1, 120.0), ('tue', 2, 112.5)]Remember
- LazyFrames build a plan;
collect()executes it - Start from
scan_*so the optimizer can prune columns and rows at the source - Polars 2 collects with the streaming engine by default; sort if order matters
Watch out for
- Calling
collect()after every step, which throws away most of the optimization.