# Polars has two version tracks, three runtime crates, and six make targets for one library

> Polars is a Rust query engine for DataFrames with Python, Rust, Node.js, R and SQL bindings, using the Apache Arrow Columnar Format for zero-copy sharing. The engine story is well told. The build story is where the detail lives and it is not in the feature list: the workspace version and the pip version are different numbers on different tracks, there are three runtime crates, six make profiles that differ only in symbols, and a default compiler flag set that drops AVX2 the moment you set LTS_CPU=1.

**pola-rs/polars** — GitHub describes it as Extremely fast Query Engine for DataFrames, written in Rust. The repository metadata lists Rust as its primary language. The metadata lists the MIT license. This article stays within the project description and details documented in the GitHub repository README.

- Repository: https://github.com/pola-rs/polars
- Website: https://docs.pola.rs
- Stars: 39,902 · Forks: 3,145
- Language: Rust
- License: MIT
- Published: 2026-08-13 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/pola-rs-polars

## The Rust crate is 0.55.1 and the Python package is on 1.44 or 2.0

The workspace manifest declares version 0.55.1. The release feed says something else entirely, and every one of the three most recent releases is a Python artefact: a 2.0.0 release candidate on 2026-09-20, 1.44.2 on 2026-09-09, and another 2.0.0 release candidate on 2026-09-02.

So the answer to what version of Polars you are running depends on which artefact you mean. The crate published to the crates.io registry that the README links as its Rust documentation is on 0.x. The package you install with pip is on 1.x, with a 2.0 release candidate in the same month.

There is an extra wrinkle in the names. The crate implementing the Python bindings is called py-polars, to distinguish it from the wrapped Rust crate polars itself, while both the Python package and the Python module are named polars, so you pip install polars and import polars.

The consequence is practical rather than cosmetic. A bug report needs the Python version and the Rust version, and they are not derivable from each other. And if you are pinning for reproducibility, pinning the Python package does not pin the engine.

## Three runtime crates sit in the workspace and the build defaults to the 32-bit one

The workspace members are not just the library. Alongside crates/ and the docs crate, there are three runtime crates under py-polars/runtime: a 32-bit runtime, a 64-bit runtime, and one named for compatibility.

That is three compiled targets for the same engine, which is a real answer to a real problem, since a 32-bit build has a hard row ceiling and a different feature set from a 64-bit one. It also means the published engine is not one binary.

Now look at the build tooling. The Makefile defines a single variable pointing at a runtime manifest, and it points at polars-runtime-32, the narrowest of the three.

The consequence is that the default reference in the build system is the 32-bit runtime while the 64-bit one is what almost everyone runs. That is a reasonable choice for keeping a fast build loop honest, since a 32-bit target compiles faster, but it means the path a contributor edits by default is not the path most users execute. The row ceiling the installation section mentions for values above 2 to the 32, roughly 4.2 billion rows, has a build-level counterpart sitting in the same tree.

## Six make targets differ only in debug assertions and symbol level

The Python install is one line, and the optional dependencies are pointed at a user guide page rather than enumerated in the file:

```sh
pip install polars
```

Compiling from source takes three steps. Install a current Rust compiler, install maturin with `pip install maturin`, then change into py-polars and pick one of six targets:

- `make build`, a slow binary with debug assertions and limited symbols, fast compile times
- `make build-debug`, the same with all symbols, producing large binaries
- `make build-release`, a fast binary without debug assertions and minimal debug symbols, long compile times
- `make build-nodebug-release`, the same without any debug symbols, slightly faster to compile
- `make build-debug-release`, the same with full debug symbols, slightly slower to compile
- `make build-dist-release`, the fastest binary at extreme compile times

Six binaries from one library, differing on two axes, neither of which is performance.

The consequence is that there is no single right answer and the file does not help you choose. debug-release and nodebug-release differ only in whether symbols are stripped, and both are described as a slight slowdown to compile. Whoever picks wrong pays in either compile time or the ability to get a useful stack trace, and the whole list sits inside a collapsed section of the install instructions.

## LTS_CPU=1 removes AVX2 and the default flag set is tuned for a modern CPU

The build targets a modern CPU by default. The Makefile spells out exactly what that means. For amd64 without LTS_CPU set, the Rust flags enable SSE3 through SSE4.2, popcnt, cmpxchg16b, avx, avx2, fma, bmi1, bmi2, lzcnt, pclmulqdq and movbe, and add a tune-cpu setting for skylake. The matching C flags mirror them.

Set LTS_CPU=1 and the list stops at cmpxchg16b. Every AVX and FMA flag is gone, along with the BMI, lzcnt and pclmul flags.

The variable is validated too. It accepts 0 or 1 and the build stops with an error otherwise, and the file notes that the values are normalised to keep the boolean handling predictable.

The README tells you to specify LTS_CPU=1 if your CPU is older and does not support AVX2. The consequence is that this is a source-build instruction, not a wheel property. If you install a prebuilt wheel on an old CPU, nothing in this file tells you which flag set it was compiled with, and the failure surfaces as an illegal instruction at runtime rather than at install time. The file also says to keep these definitions synchronised with the release workflow, so the flag set exists in two places that can drift.

## Architecture detection warns and continues rather than refusing to build

The Makefile detects your CPU in two branches, one for Windows and one for everything else.

On Windows it reads PROCESSOR_ARCHITECTURE and maps AMD64, x86 and ARM64 to canonical names. On Unix it shells out to uname -m and maps x86_64 and x64 to amd64, arm64 and aarch64 to arm64, and anything matching a pattern ending in 86 to x86.

Both branches end the same way. An architecture that matches nothing produces a make warning naming the value, and then the architecture variable is set to the literal string unknown, and the build continues.

That string then flows into the flag selection, where the amd64 branch has a full feature list and the LTS_CPU branch has a shorter one. There is no equivalent listing here for the unknown case, and there is no hard stop.

The consequence is that building on an architecture this file has not seen before does not fail loudly at detection. It fails later, in the compiler, with whatever feature set the fallback selects, or it succeeds and produces a binary tuned for nothing in particular. For a project that treats CPU dispatch as a first-class concern, treating an unrecognised CPU as a warning rather than an error is a deliberate looseness, and it is the kind you find out about on new hardware.

## The performance claim is a superlative and the file contains no number

The worked example is a lazy query, and the lazy part is the point. Nothing touches disk until the final call:

```python
import polars as pl

df = (
    pl.scan_parquet("orders.parquet")
    .filter(pl.col("status") == "shipped")
    .group_by("customer_id")
    .agg(
        pl.col("amount").sum().alias("total"),
        pl.len().alias("n_orders"),
    )
    .sort("total", descending=True)
    .collect()
)
```

The scan reads a Parquet file lazily, the filter and the group-by are expressions, the aggregation produces two aliased columns, and collect at the end is where the work happens. That is the shape the engine is built around: an expression tree, optimized, then run in parallel across the available cores.

The Performance section around it is three sentences. Polars is very fast. In fact, it is one of the best performing Dataframe solutions available. See the PDS-H benchmarks results.

There is no figure anywhere in that section, and the evidence is a link to a separate page. The one concrete data-handling claim in the file is in the next section, and it is hedged: the streaming engine drastically reduces memory requirements, so you might be able to process your 250GB dataset on your laptop.

Might be able to. That is an illustration, not a capacity statement, and it comes with a mechanism you have to remember to use. Streaming is selected at collect time with `collect(engine='streaming')` rather than set once for a session.

The consequence is that the two ways this file overstates and undersells point the same direction. The ranking is asserted without a number, and the number that does appear is qualified. A reader who skips the collect argument on a dataset that does not fit gets the in-memory engine, and finds out at the point of memory exhaustion rather than at the point of configuration.

## Cluster scale, GPU execution and plugins are all documented outside this repository

The feature list makes three claims that this tree cannot deliver on its own, and each one is a link.

Larger-than-RAM is real and local, handled by the streaming engine. The other two are not. GPU support is described as optionally accelerating queries on NVIDIA GPUs, with no build flag, no device list and no feature name in the file. Extensibility points at a documentation page for I/O and Expression plugins.

Then there is a whole section for what happens when one machine is not enough. Distributed Polars is one sentence and a link to the Polars Cloud documentation, framed as horizontally scaling your Polars query on a cluster.

So the repository is a single-machine engine plus language bindings, and the three capabilities that would take it past one machine are documented somewhere else.

The consequence is a mismatch between the feature list and the deliverable that is easy to miss when you are choosing a tool. GPU acceleration, plugins and horizontal scale are all real, and none of them is a flag you set in this tree. If any of the three is the reason you are evaluating Polars, you are evaluating a document, not a repository.

## Conclusion

Use Polars if you want a DataFrame engine with a streaming path for data larger than memory, a lazy optimizer, and Arrow-compatible interchange across five language bindings, and if you are building from source anyway. Do not adopt it expecting a stable version number to pin, because the Rust crate and the Python package are on separate tracks and the Python line is currently in release candidates. Before you build, check four things: whether your CPU supports the instruction sets the default flags target, since LTS_CPU=1 is the documented escape and it removes AVX2 entirely; which of the six make profiles you want, because they trade compile time against symbol availability; that you pass collect(engine='streaming') when the data does not fit, because streaming is chosen at collect time; and where horizontal scaling lives, since the cluster path is documented outside this repository.

## FAQ

### What are Polars?

An analytical query engine for DataFrames, written in Rust with multi-threaded, vectorized SIMD execution. It supports lazy and eager execution with query optimization out of the box, a streaming engine for data larger than RAM, bindings for Python, Rust, Node.js, R and SQL, and it uses the Apache Arrow Columnar Format for zero-copy data sharing.

### Are Polars better than pandas?

The README makes no comparison with pandas. It says Polars is very fast and one of the best performing Dataframe solutions available, and points to the PDS-H benchmarks for results, without giving a figure in the file itself.

### What is the current version of Polars?

There are two answers. The workspace manifest declares 0.55.1 for the Rust crate, while the Python package is on a separate track, with releases 1.44.2 on 2026-09-09 and 2.0.0-rc.2 on 2026-09-20. The crate implementing the Python bindings is called py-polars, while both the Python package and module are named polars.

### how to install polars

For Python, `pip install polars`, with optional dependencies covered in the user guide. Building from source needs a current Rust compiler, maturin installed with `pip install maturin`, and then one of six make targets run from py-polars. Specify LTS_CPU=1 if your CPU is older and lacks AVX2. The installation guide covers the advanced cases, including more than 2^32 rows, an old CPU, and an x86-64 build of Python on Apple Silicon under Rosetta.

### how to use polars

Queries are composed from expressions and can be built lazily and collected at the end, for example scanning a Parquet file, filtering, grouping, aggregating, sorting and then collecting. To process data that does not fit in memory, collect with `collect(engine='streaming')` so the query runs in a streaming fashion.

## Sources

- [Official documentation](https://docs.pola.rs)
- [Official README](https://github.com/pola-rs/polars#readme)
- [Project repository](https://github.com/pola-rs/polars)
- [Release notes](https://github.com/pola-rs/polars/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pola-rs-polars
