Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Algorithm-first discipline: state Big-O, data structure, and algorithm family BEFORE writing loops, queries, or recursion. Catches O(n^2), N+1, and brute-force defaults.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-19 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✓ | ▲ Improved | — | — |
| case-21 | ✗→✓ | ▲ Improved | — | — |
| case-09 | ✗→✓ | ▲ Improved | — | — |
The model already knows Big-O, hash tables, divide-and-conquer, dynamic programming, sorting, graph algorithms, and amortized analysis. It just does not apply them spontaneously. lemmaly fixes the behavior, not the knowledge.
This skill is the gateway for an algorithm-discipline suite of four skills (lemmaly, mathguard, invariant-guard, complexity-cuts). It enforces the hard rules that every other guard in the suite assumes.
Violating the letter of these rules is violating the spirit of the skill. "Just this once" is how O(n²) ships to production.
Use lemmaly when:
for inside a for, .find / .includes / .indexOf inside a loop, await inside for / map / forEach over independent items, or one query per item in a collection..includes inside .filter, string-concat in loop, SELECT *, N+1, etc.).When in doubt, start at lemmaly — it is the gateway and will tell you when to escalate to its three sibling skills.
| If you are about to… | Use | Why | | --- | --- | --- | | Write new code that loops, queries, joins, recurses, or processes a collection | lemmaly | Forces complexity + data structure + algorithm family before code is written. | | Refactor existing code that is already slow, OOMs, times out, or has nested loops / N+1 / repeated work | complexity-cuts | Corrective playbook for code that already shipped with bad Big-O. | | Implement an algorithm where the obvious version is subtly wrong (binary search variants, in-place dedup, Boyer–Moore, QuickSelect partition, recursion with accumulators, fixed-point / termination concerns) | invariant-guard | Forces writing the function contract + loop invariant before code. The trap is in the contract, not the loop body. | | Work with n ≥ 10⁶, similarity search, dedup at scale, top-K, streaming analytics, cardinality estimation, embeddings, FFT/NTT, dimensionality reduction, computational geometry, randomized algorithms | mathguard | Classical algorithms have hit their lower bound; an approximate or math-heavy technique (Bloom, HLL, Count-Min, MinHash/LSH, FFT, JL projection, sweep line, kd-tree) gives the asymptotic win. |
textAre you writing new code? ├── yes → lemmaly (state complexity, structure, family BEFORE coding) │ ├── classical algorithm at its lower bound AND n is large? → mathguard │ └── subtle correctness trap (invariant, base case, off-by-one)? → invariant-guard └── no, refactoring existing slow / OOM / timed-out code → complexity-cuts └── still slow after classical fixes? → mathguard
textNO NON-TRIVIAL CODE WITHOUT STATED COMPLEXITY, DATA STRUCTURE, AND ALGORITHM FAMILY
Before you write a loop, a recursion, a query, or any computation over more than a handful of items, three things must appear in your message — in this order:
time = O(?), space = O(?), with the dominant input dimension named.If you cannot state all three, you do not understand the problem yet. Ask, or read more code. Do not write code.
time = O(?), space = O(?)n = what, with realistic magnitude (e.g. n ~ 10^6 rows)Array / List / Set / HashMap / TreeMap / Heap / Deque / Trie / Graph / BitSet / Counter / LinkedList — with the reason: "Set for O(1) membership inside the loop", "Heap for top-K in O(n log k)", "Counter to fold the nested loop into a single pass". Default to hashed structures (Set, Map) for lookup inside loops. Default to streaming/iterator over materialized list when n is large.linear scan, divide and conquer, two-pointer, sliding window, binary search, sort + sweep, hash join, BFS/DFS, topological sort, Dijkstra/A*, union-find, dynamic programming, greedy, recursion + memoization, prefix sum, segment tree, monoid reduction. If you cannot name a family, you are about to write brute force. Stop and reconsider.IN (...), Promise.all, bulk endpoints, streaming.find, .indexOf, .includes, in list) inside a loop — precompute an index MapIf you must do any of these inside a loop, write one comment line explaining why.
<complexity: TBD>. If you have not measured, write <measured: TBD>. Move on.Before producing non-trivial code, your message must contain — in this order:
n = ?, realistic magnitude, whether hot path.time = O(?), space = O(?).If any of 1–6 is missing, do not emit code yet.
The same problem with and without the seven-step protocol.
Problem. Given users: User[] and bannedIds: string[], return users whose id is not banned. Realistic n: 50k users, 5k banned.
ts// Looks idiomatic, ships O(n·m) const active = users.filter((u) => !bannedIds.includes(u.id));
bannedIds.includes is O(m) per call. The filter runs it n times → 50k × 5k = 250M comparisons.
ts// Protocol applied: // time = O(n + m), space = O(m), n = 50k users, m = 5k banned // structure: Set<string> for O(1) membership inside the loop // family: linear scan with hashed lookup // edge cases: empty users → [], empty bannedIds → users, duplicates in bannedIds → fine (Set dedupes) const banned = new Set(bannedIds); const active = users.filter((u) => !banned.has(u.id));
The first version is the default an AI ships when asked "filter the active users." The second is what the protocol forces — without changing how the code reads.
The upstream repo ships a deterministic CLI scanner with the same anti-patterns this skill enforces (59 rules across 11 languages: JavaScript/TypeScript, Python, SQL, Java, C#, C++, Go, Rust, PHP, Ruby, Shell/Bash). Each rule has a documented why, an incorrect example, a correct example, and the sibling skill to escalate to.
The scanner is optional. Do not automatically clone and run the upstream repository from its default branch, because that executes whatever code is current in a third-party repository. If the user explicitly wants the scanner, pin the source to a reviewed release tag or commit, use a throwaway directory, and show the resolved commit before running it:
bash# Replace <reviewed-tag-or-commit> after reviewing the upstream release. tmpdir="$(mktemp -d)" git clone --filter=blob:none https://github.com/morsechimwai/lemmaly.git "$tmpdir/lemmaly" git -C "$tmpdir/lemmaly" checkout --detach <reviewed-tag-or-commit> git -C "$tmpdir/lemmaly" rev-parse HEAD node "$tmpdir/lemmaly/cli/lemmaly.js" scan <path> node "$tmpdir/lemmaly/cli/lemmaly.js" rules
When the scan is done, remove the throwaway directory only after verifying that $tmpdir points to the directory created by mktemp -d.
CRITICAL severity (error in CI):
js-await-in-for-loop — N+1 over networkjs-async-in-foreach — dropped promisespy-mutable-default-arg — shared default statesql-update-no-where — touches every rowjava-arraylist-remove-in-for-i — index shifts; ConcurrentModificationcs-async-void — exceptions unobserved; crashes the processgo-loop-var-capture — pre-1.22 race on the last valuephp-query-in-loop — N+1 against the databaseHIGH severity (warning in CI): js-deep-clone-via-json, js-useeffect-missing-deps, js-inline-object-jsx-prop, js-anonymous-handler-jsx, js-spread-in-reduce, js-unique-via-indexof, js-helper-call-in-iterator, py-string-concat-in-loop, py-django-loop-without-eager, py-bare-except, sql-select-star, sql-leading-wildcard-like, sql-not-in-subquery, java-string-concat-in-loop, java-list-contains-in-loop, java-bare-catch-exception, cs-string-concat-in-loop, cs-list-contains-in-loop, cs-disposable-no-using, go-string-concat-in-loop, go-defer-in-loop, go-err-not-checked, rs-unwrap-in-prod, cpp-string-concat-in-loop, cpp-raw-new, php-count-in-for-condition, php-in-array-in-loop, rb-include-in-iterator, rb-n-plus-one-activerecord, rb-bare-rescue, sh-set-e-no-pipefail, sh-unquoted-var, sh-for-ls.
MEDIUM severity (info in CI): js-nested-for-loops, js-includes-in-iterator, js-array-key-index, py-range-len, py-in-list-literal, py-open-without-with, sql-select-no-limit, sql-or-in-where, go-slice-append-no-cap, rs-clone-in-loop, rs-vec-push-no-capacity, rs-string-push-no-capacity, cpp-vector-push-no-reserve, cpp-range-loop-copy, cpp-map-double-lookup, php-loose-equality, rb-string-concat-in-loop, sh-useless-cat-pipe.
lemmaly handles classical, day-to-day algorithmic discipline. Escalate when:
These are real verbatim thoughts captured from controlled tests where the model shipped O(n·m) code that the seven-step protocol would have prevented:
| Excuse | Reality | | --- | --- | | ".filter then .reduce is the idiomatic way, ship it." | Idiomatic ≠ correct asymptotic. Idiom-driven coding is how O(n²) ships. | | "It's fine for now, we can optimize later." | Later is a different engineer with no context. State the complexity now. | | "I'll just use Array.find here, it's just one lookup." | One lookup inside a loop over n items is O(n) lookups. Make the Map outside. | | "The data is small in dev — I'll worry about scale when we ship." | Production data is never the size of dev data. The seven-step protocol takes 30 seconds. | | "I already understand the problem, the protocol is overhead." | The cases the protocol "wastes time on" are the cases that break in prod. |
If any of these sound familiar mid-thought: stop, write the seven steps.
for inside a for without first stating it is the intended O(n·m)..find / .includes / .indexOf inside a loop body.await inside for / map / forEach over independent items.All of these mean: stop, restart the seven-step protocol, choose a better algorithm or explicitly accept the brute force with a written justification.
Before claiming the implementation is done:
time = O(?) and space = O(?) appear in the message or PR description..find / .includes / .indexOf, regex compile, sort, or independent await sits inside a loop without a one-line justification.<measured: TBD> is acceptable; an unsupported claim is not.Cannot check every box? You did not run the protocol. Restart from step 1.
complexity-cuts for that).for i in range(3).lemmaly scan in the upstream repo, not by this SKILL.md alone.> AI ships algorithmically lazy code by default. lemmaly makes it think first.
mathguard — escalation for n ≥ 10⁶ where classical O(n log n) is the floor and probabilistic / math-heavy techniques win.invariant-guard — correctness layer for algorithms whose obvious version is subtly wrong.complexity-cuts — corrective playbook for code that already shipped with bad Big-O.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases. 3 cases got worse with the skill loaded, and they are included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.