Skip to main content

Command Palette

Search for a command to run...

How BrassCoders Catches Slow AI-Generated Code

AI assistants write O(N²) loops that pass every test and crawl at scale. BrassCoders flags all four patterns where Bandit and Semgrep catch none.

Updated
4 min readView as Markdown
How BrassCoders Catches Slow AI-Generated Code
C
Founder at Copper Sun. I build developer tools, with a current focus on AI code safety. BrassCoders is our static scanner for Python codebases — it catches the structural bugs AI assistants write and hands findings to your AI for triage.

AI assistants write code that passes every test and crawls in production. A 2024 efficiency benchmark, ENAMEL, found frontier models reach only a fraction of expert-level efficiency even when their output is functionally correct. The failure is quiet: the unit test is green, the reviewer sees clean code, and the O(N²) loop only shows itself at volume. BrassCoders's performance scanner catches four of these patterns straight from the source, before the code ever runs.

The Four Patterns BrassCoders Flags

BrassCoders's performance scanner detects four AST-signatured slow patterns that AI assistants reproduce: quadratic string building, prepend-in-a-loop, nested-loop joins, and unbounded reads. It caught all four in the June 2026 benchmark, where the standard security and lint tools caught none.

  • O(N²) string concatenation: result += row inside a loop. Python strings are immutable, so every concatenation allocates a new string and copies both halves. Unnoticeable on 100 rows; the function hangs on 100,000.
  • list.insert(0) in a loop: building a list by prepending. Each insert shifts every existing element right, turning an O(N) build into O(N²). The fix is append then reverse, or a deque.
  • Triple-nested loop as a join: iterating three lists to find matches when a dict lookup would make it O(N). AI assistants reach for the nested loop when the data relationships aren't spelled out in the prompt.
  • Unbounded while True: an accumulation loop with no timeout, size cap, or iteration limit. Fine for a bounded task; a resource-exhaustion vector when a caller controls the input size.

The full field guide to how these show up in real AI-generated code is in AI-Coder Performance Bugs in the Wild.

Why the Standard Scanners Miss Them

BrassCoders treats the performance gap as a scope mismatch, not a scanner deficiency. Bandit, Semgrep, and Pylint were calibrated on human-written code, and a human engineer rarely writes csv += line in a hot loop. The pattern wasn't a common review concern, so the standard tools never grew a rule for it.

AI assistants changed the input distribution. They reproduce these shapes from training data full of tutorials and demo scripts that used small datasets, where the slow pattern reads naturally and runs fine. The research backs the pattern: EffiBench and Mercury both measure generated code consuming far more time and memory than the canonical efficient solution while still passing the functional tests. Correct-but-slow is the default the benchmarks find, not the exception.

Deterministic, Not Profiled

BrassCoders catches these patterns from the abstract syntax tree, before the code runs, because each one has a fixed structural signature. The scanner visits the node types that define the pattern — a concatenation operator whose target is reassigned inside a For body, an insert(0) call inside a loop — and emits a finding with the file, the line, and the evidence string. No workload, no profiler, no model deciding what looks slow.

That determinism is what makes it a pre-merge gate. A profiler needs a running process and a large enough input to surface the hotspot, which usually means production. An AST rule needs only the source, so the finding lands in CI on the pull request, before anyone ships. The same file produces the same finding on every run.

What It Catches, and What It Doesn't

BrassCoders flags the four signatured patterns; it does not claim to find arbitrary algorithmic inefficiency. A novel O(N²) buried in a custom data structure has no fixed signature, so no deterministic rule will catch it — that's a judgment call for a profiler under load or a reviewer reading the logic. BrassCoders reports the patterns it can prove from the structure and leaves the open-ended performance review to you.

The honest pairing is scanner plus profiler. BrassCoders catches the four known anti-patterns early and cheaply, on every scan. A sampling profiler like py-spy or Scalene then confirms the real cost of a flagged hotspot under load, and surfaces the slow code that doesn't match a known shape. The reproducible head-to-head against a frontier model is in the AI-coder bug benchmark, with the underlying efficiency research in the performance anti-patterns research.

Run It

The performance scanner runs automatically in every scan — there's no flag to remember. Install BrassCoders and scan:

pipx install brasscoders
brasscoders --offline scan

Performance findings land in .brass/ai_instructions.yaml next to the security, secrets, and correctness findings, ready to hand to Claude Code or Cursor. For the full set of detectors that run in the same pass, see what BrassCoders detects.