How Biome Stays Fast: Incremental Parsing and Composable Rules in Practice
Biome stays fast through incremental parsing and a single composable config schema. Here's how both work, a realistic overrides setup, and the precedence trap to avoid.
14 Jul 2025, 02:10 UTC

The problem: linting that can't keep up with the editor
If you've ever watched an ESLint-based editor integration take two or three seconds to flag an unused import in a large TypeScript file, you've felt the cost of full-file reanalysis. Every keystroke invalidates the previous parse, so the toolchain reparses everything, re-runs every rule, and only then updates diagnostics. On a 5,000-line generated file or a hot monorepo package, that latency compounds into a genuinely degraded editing experience.
Biome takes a different architectural approach, and understanding it helps you decide whether it fits your project — and how to configure it so you actually benefit. Two design decisions matter most: incremental parsing and composable rule configuration.
Incremental parsing: reuse the tree, not just the result
Biome's parser is built on a resilient, rowan-style syntax tree (the same family of design used by rust-analyzer). The practical consequence: when you edit a file, the parser can reuse unchanged subtrees instead of rebuilding the entire AST. A one-line change inside a function body doesn't invalidate the module's other declarations.
This is why Biome's language server feels responsive even in files where traditional tools stutter. The analyzer works on the incrementally updated tree, so diagnostics and code actions only recompute over the affected region's scope.
There's a caveat worth knowing: incremental parsing assumes a reasonably stable view of the file. If your workflow involves files being rewritten frequently by external generators (codegen pipelines, watch-mode bundlers writing into src), you can end up with the language server holding a stale tree until it resyncs. The fix is mundane — restart the Biome daemon or LSP session — but it explains the occasional "Biome says the error is gone but the editor still shows it" confusion.
Composable rules: per-directory overrides without a second config file
Biome 1.x consolidated its configuration into a single biome.json schema that covers both linting and formatting — the roles ESLint and Prettier configs traditionally split. The feature that matters day-to-day is overrides: rule sets that apply only to matched file patterns.
A realistic example. Your app code should follow strict rules, but your test files legitimately need any and non-null assertions, and your generated API client shouldn't be linted at all:
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": { "noExplicitAny": "error" }
}
},
"overrides": [
{
"include": ["**/*.test.ts", "**/*.test.tsx"],
"linter": {
"rules": {
"suspicious": { "noExplicitAny": "off" },
"style": { "noNonNullAssertion": "off" }
}
}
},
{
"include": ["src/generated/**"],
"linter": { "enabled": false },
"formatter": { "enabled": false }
}
]
}Run this from your project root (wherever biome.json lives) with the Biome CLI installed — either via npm exec biome or a global install. No special permissions are needed; it only reads and, with --write, modifies files you already own.
# Check what the config actually does before applying it
biome lint src/generated/client.ts # expect: no diagnostics, linter disabled
biome lint src/utils/parser.test.ts # expect: no noExplicitAny errors
biome lint src/app.ts # expect: strict rules still applyThe check commands are safe — they only report. Add --write only after the reported diagnostics match your expectations, and commit your working tree first so any unwanted autofixes are a git checkout away.
The trade-off: precedence is on you to document
Overrides are evaluated in order, and later entries win for overlapping patterns. That's powerful but creates a real hazard: if src/generated/** also matches a broader src/** override you add later, the "disabled" generated directory silently becomes linted again. Biome won't warn you that your overrides conflict — the last match simply applies.
Two habits prevent most pain here. First, order overrides from most general to most specific, and put a one-line comment convention in your team's docs explaining why each block exists (JSON doesn't allow comments, but Biome also accepts biome.jsonc if you want them inline). Second, after editing overrides, spot-check one file per override block with the CLI, as shown above. It takes two minutes and catches precedence mistakes before they reach CI.
Also verify your version before trusting schema-specific behavior: biome --version. The unified config schema evolved across 1.x releases, and override semantics you read about in a blog post (including this one) should be checked against the schema for your installed version — the $schema URL in the config above pins exactly that.
Where this leaves you
Biome's speed isn't magic — it's an incremental syntax tree plus a single-pass architecture, and its configuration flexibility comes from one composable schema rather than a plugin ecosystem. If you're evaluating it, the fastest honest test is: point it at your largest source directory, run biome check ., then add one override block for your test files. If the diagnostics are correct and the editor stays responsive on your biggest file, the architecture is delivering what it promises. If you hit stale diagnostics after codegen runs, you now know why — and that a daemon restart, not a config change, is the fix.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.