Skip to content

Support regex duplicate named capture groups#291

Merged
frostney merged 3 commits into
mainfrom
t3code/duplicate-named-capture-groups
Apr 12, 2026
Merged

Support regex duplicate named capture groups#291
frostney merged 3 commits into
mainfrom
t3code/duplicate-named-capture-groups

Conversation

@frostney
Copy link
Copy Markdown
Owner

  • Allow duplicate names across disjunction alternatives
  • Resolve named backreferences and match groups correctly
  • Add docs and RegExp coverage for the new behavior

frostney and others added 2 commits April 12, 2026 21:49
- Allow duplicate names across disjunction alternatives
- Resolve named backreferences and match groups correctly
- Add docs and RegExp coverage for the new behavior
…5) labels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frostney frostney added the spec compliance Mismatch against official JavaScript/TypeScript specification label Apr 12, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 810c0269-240b-465f-ae44-d6c3b84bb5b5

📥 Commits

Reviewing files that changed from the base of the PR and between f19ac39 and 3ac60d6.

📒 Files selected for processing (2)
  • tests/built-ins/RegExp/prototype/exec.js
  • units/Goccia.RegExp.Engine.pas
✅ Files skipped from review due to trivial changes (1)
  • tests/built-ins/RegExp/prototype/exec.js

📝 Walkthrough

Walkthrough

Adds support for duplicate named capture groups across disjunction alternatives: parser tracks disjunction paths, backreferences resolve to the group from the participating alternative, duplicates within the same alternative throw an early error, and runtime builds the groups object in two passes so values from participating alternatives are preserved.

Changes

Cohort / File(s) Summary
Documentation
docs/built-ins.md, docs/language-restrictions.md
Clarify RegExp semantics for duplicate named capture groups across disjunction alternatives: groups reflects the participating alternative, non-participating duplicates become undefined, same-alternative duplicate names are an early error, and \k<name> resolves within the same alternative branch.
Tests
tests/built-ins/RegExp/prototype/exec.js
Add extensive tests covering positive and negative cases for duplicate named capture groups: exec/match/matchAll/replace interactions, non-capturing-group and nested/disjunction variants, SyntaxError cases for same-branch duplicates, null-prototype check, and non-match behavior.
Engine / Parser
units/Goccia.RegExp.Engine.pas
Track per-named-group DisjunctionPath; add PathsShareBranch, ResolveNamedBackreference; enforce early error for duplicate names that share a branch; replace prior single-index backreference substitution with context-aware resolution.
Runtime
units/Goccia.RegExp.Runtime.pas
Change BuildMatchArray to two-pass construction of groups: initialize all named keys to undefined, then assign matched values only for participating groups to avoid overwrite ordering issues with duplicate names.
Spec Metadata
units/Goccia.Spec.pas
Add ES2025 feature entry "Duplicate Named Capture Groups" (expand features array), reformat spacing for existing entries.

Sequence Diagram

sequenceDiagram
    participant Parser as Parser/Engine
    participant AltStack as Disjunction Stack
    participant Validator as Duplicate-name Validator
    participant Preprocessor as Preprocess / Backref Resolver
    participant Runtime as Runtime (exec)
    participant Builder as BuildMatchArray
    participant Result as Groups Object

    Parser->>AltStack: Push/pop disjunction alternative indices
    Parser->>Parser: Collect named groups with DisjunctionPath
    Parser->>Validator: Check for same-branch duplicate names
    Validator-->>Parser: Throw error or continue
    Preprocessor->>Parser: ResolveNamedBackreference using DisjunctionPath
    Runtime->>Builder: Execute regex and pass captures
    Builder->>Result: Initialize all named keys to undefined (pass 1)
    Builder->>Result: Assign matched values for participating groups (pass 2)
    Result-->>Runtime: Return finalized groups object
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the main objectives but lacks testing evidence and documentation updates mentioned in the template, making it incomplete relative to the repository's standard. Add checkbox confirmations for testing (JavaScript/TypeScript end-to-end tests and optional Pascal tests), and explicitly confirm documentation updates (docs/built-ins.md, docs/language-restrictions.md).
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding support for regex duplicate named capture groups, which is the primary feature across all file modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the new feature New feature or request label Apr 12, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/built-ins/RegExp/prototype/exec.js (1)

137-147: Assert SyntaxError explicitly in the negative cases.

These tests are checking user-visible semantics, so toThrow(SyntaxError) is a safer guard than a generic throw assertion.

Suggested tightening
 test("exec with duplicate names in same alternative throws SyntaxError", () => {
   expect(() => {
     new RegExp("(?<x>a)(?<x>b)");
-  }).toThrow();
+  }).toThrow(SyntaxError);
 });
 
 test("exec with duplicate names in nested same alternative throws SyntaxError", () => {
   expect(() => {
     new RegExp("(?<x>a(?<x>b))");
-  }).toThrow();
+  }).toThrow(SyntaxError);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/RegExp/prototype/exec.js` around lines 137 - 147, The tests
use toThrow() with new RegExp("(?<x>...)") negative cases but should assert the
exact error type; update both test blocks ("exec with duplicate names in same
alternative throws SyntaxError" and "exec with duplicate names in nested same
alternative throws SyntaxError") to call expect(...).toThrow(SyntaxError)
instead of toThrow(), ensuring the RegExp constructor invocations (new
RegExp("(?<x>a)(?<x>b)") and new RegExp("(?<x>a(?<x>b))")) are asserted to throw
SyntaxError explicitly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@units/Goccia.RegExp.Engine.pas`:
- Around line 191-203: Add the repo's spec-annotation comment immediately above
each helper implementation using the required format; for example, add a line
like "// ES2025 §22.2.1.1 PathsShareBranch(APathA, APathB)" directly above the
PathsShareBranch function, and do the same for the other helper functions in the
same block (the helpers spanning the subsequent implementations) using their
corresponding ES spec section numbers and SpecMethodName(specParams) signatures
so each FreePascal function is annotated per the coding guidelines and placed
immediately above the function body in the implementation section.
- Around line 245-276: The fallback that builds '(?:\1|\2|...)' from
ANamedGroups/AllIndices is wrong because it lets non-participating
backreferences match empty; remove the AllIndices/ANamedGroups fallback and
instead only emit the alternation built from CompatibleIndices when
CompatCount>0, and when CompatCount=0 emit a failing construct (e.g. a negative
lookaround or '(?!)') so the pattern cannot silently succeed — update the code
paths that set Result (the blocks that currently reference AllIndices,
ANamedGroups, CompatCount, CompatibleIndices and build Result) to implement this
change.

---

Nitpick comments:
In `@tests/built-ins/RegExp/prototype/exec.js`:
- Around line 137-147: The tests use toThrow() with new RegExp("(?<x>...)")
negative cases but should assert the exact error type; update both test blocks
("exec with duplicate names in same alternative throws SyntaxError" and "exec
with duplicate names in nested same alternative throws SyntaxError") to call
expect(...).toThrow(SyntaxError) instead of toThrow(), ensuring the RegExp
constructor invocations (new RegExp("(?<x>a)(?<x>b)") and new
RegExp("(?<x>a(?<x>b))")) are asserted to throw SyntaxError explicitly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3077d45e-68a0-4af6-8c5a-eb9acde487c1

📥 Commits

Reviewing files that changed from the base of the PR and between 100551e and f19ac39.

📒 Files selected for processing (6)
  • docs/built-ins.md
  • docs/language-restrictions.md
  • tests/built-ins/RegExp/prototype/exec.js
  • units/Goccia.RegExp.Engine.pas
  • units/Goccia.RegExp.Runtime.pas
  • units/Goccia.Spec.pas

Comment thread units/Goccia.RegExp.Engine.pas Outdated
Comment on lines +245 to +276
if CompatCount = 0 then
begin
// Backreference outside the disjunction — collect all groups with this name
SetLength(AllIndices, AllCount);
AllCount := 0;
for I := 0 to High(ANamedGroups) do
if ANamedGroups[I].Name = AName then
begin
AllIndices[AllCount] := ANamedGroups[I].Index;
Inc(AllCount);
end;
// Emit alternation: (?:\1|\2|...) — the participating group's backreference
// succeeds while non-participating ones either match empty or fail through
Result := '(?:';
for I := 0 to AllCount - 1 do
begin
if I > 0 then
Result := Result + '|';
Result := Result + '\' + IntToStr(AllIndices[I]);
end;
Result := Result + ')';
Exit;
end;
// Multiple compatible groups — emit alternation of compatible ones
Result := '(?:';
for I := 0 to CompatCount - 1 do
begin
if I > 0 then
Result := Result + '|';
Result := Result + '\' + IntToStr(CompatibleIndices[I]);
end;
Result := Result + ')';
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In the Free Pascal/Delphi RegExpr (TRegExpr) engine, does a numeric backreference like \1 fail or match an empty string when the referenced capture group did not participate in the current match?

💡 Result:

In the Free Pascal/Delphi TRegExpr engine, a numeric backreference like \1 matches an empty string when the referenced capture group did not participate in the current match. It does not fail the overall match.

Citations:


The (?:\1|\2|...) fallback is incorrect and will make patterns too permissive.

TRegExpr's numeric backreferences match an empty string when the referenced capture group did not participate in the current match — they do not fail. This means the current approach allows non-participating groups' backreferences to silently succeed via empty match as fallback alternatives, violating the semantics.

Example: For the pattern (?:(?<x>a)|(?<x>b))\k<x> on input a:

  1. First part matches a, capturing group 1
  2. Second part emits (?:\1|...) with \1 for group 1 and \2 for any other named group (if compiled that way)
  3. \1 fails to match (no second a), but \2 matches empty (group 2 never participated)
  4. The overall pattern incorrectly accepts a instead of requiring aa or bb

The fallback must not use unmatched backreferences as alternatives. Instead, only include references to groups that actually participated, or redesign the fallback to enforce that the backreference must match a non-empty capture.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.RegExp.Engine.pas` around lines 245 - 276, The fallback that
builds '(?:\1|\2|...)' from ANamedGroups/AllIndices is wrong because it lets
non-participating backreferences match empty; remove the AllIndices/ANamedGroups
fallback and instead only emit the alternation built from CompatibleIndices when
CompatCount>0, and when CompatCount=0 emit a failing construct (e.g. a negative
lookaround or '(?!)') so the pattern cannot silently succeed — update the code
paths that set Result (the blocks that currently reference AllIndices,
ANamedGroups, CompatCount, CompatibleIndices and build Result) to implement this
change.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The web query result is incorrect — TRegExpr fails (does not match empty) on backreferences to non-participating groups. Empirically verified:

// TRegExpr fails \1 when group 1 didn't participate
new RegExp("(a)|(b)\\1").exec("b")  // → null

// Alternation correctly falls through to the participating group
new RegExp("(?:(a)|(b))(?:\\1|\\2)").exec("aa")  // → "aa" ✓
new RegExp("(?:(a)|(b))(?:\\1|\\2)").exec("bb")  // → "bb" ✓
new RegExp("(?:(a)|(b))(?:\\1|\\2)").exec("a")   // → null ✓
new RegExp("(?:(a)|(b))(?:\\1|\\2)").exec("ab")  // → null ✓

// Concatenation approach fails entirely (non-participating backref aborts match)
new RegExp("(?:(a)|(b))\\1\\2").exec("aa")  // → null ✗
new RegExp("(?:(a)|(b))\\1\\2").exec("bb")  // → null ✗

The alternation (?:\1|\2) is the correct approach precisely because TRegExpr fails on non-participating backreferences — the | lets the engine skip the failed backref and try the next one. Added a clarifying comment in 3ac60d6 documenting this TRegExpr behavior, plus a test covering the outside-disjunction backreference case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frostney, thank you for the empirical verification and the correction — my original review comment was wrong. The web search result I relied on was incorrect in its characterization of TRegExpr's behavior.

To confirm the key distinction your examples demonstrate:

  • JavaScript's regex engine: backreferences to non-participating groups match an empty string (zero-length), so they silently succeed — (?:\1|\2) would be problematic there.
  • TRegExpr: backreferences to non-participating groups fail, so (?:\1|\2) is exactly correct — the | lets the engine skip the failed backreference and try the participating group's backreference next.

The (?:\1|\2|...) alternation strategy is the right design for this engine, and the clarifying comment you added in 3ac60d6 along with the outside-disjunction backreference test makes the rationale clear. I apologize for the noise.


✏️ Learnings added
Learnt from: frostney
Repo: frostney/GocciaScript PR: 291
File: units/Goccia.RegExp.Engine.pas:255-286
Timestamp: 2026-04-12T21:29:56.163Z
Learning: In frostney/GocciaScript (units/Goccia.RegExp.Engine.pas), TRegExpr (the Free Pascal regex engine used as the backend) **fails** — does not match empty — when a numeric backreference targets a non-participating capture group. This is the opposite of JavaScript's native regex engine behavior (which matches empty/zero-length for non-participating backreferences). Because TRegExpr fails on non-participating backreferences, the `(?:\N1|\N2|...)` alternation strategy used in `ResolveNamedBackreference` is correct for handling duplicate named capture groups: the engine skips the failing non-participating backreference via `|` and matches the participating group's backreference. Do NOT flag this alternation approach as incorrect or suggest replacing it with a failing construct like `(?!)` — the `|`-based alternation is the intended and empirically verified correct behavior for TRegExpr.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: frostney
Repo: frostney/GocciaScript PR: 172
File: units/Goccia.Compiler.Expressions.pas:169-178
Timestamp: 2026-04-03T14:07:41.850Z
Learning: In frostney/GocciaScript (units/Goccia.Compiler.Expressions.pas), `CompileRegexLiteral` intentionally uses `OP_GET_GLOBAL` to fetch the `RegExp` constructor and then `OP_CALL` to create the regex object. This is safe because the `RegExp` global binding is stable and not user-mutable in this runtime, so there is no behavioral divergence from the interpreter path (`TGocciaRegexLiteralExpression.Evaluate` → `CreateRegExpObject`). Do NOT flag this as a semantic divergence or suggest a dedicated intrinsic opcode for regex literal creation.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 278
File: units/Goccia.Parser.pas:1010-1023
Timestamp: 2026-04-12T09:44:48.683Z
Learning: In frostney/GocciaScript (units/Goccia.Parser.pas, SplitTemplateAtBoundaries and the original ParseTaggedTemplate), the brace-counting used to find the closing `}` of a template interpolation expression (`${...}`) is NOT lexically aware — it does not account for `{`/`}` inside string literals, comments, or nested templates within the expression. This is a known pre-existing limitation that predates this codebase area; it was present in the original `ParseTaggedTemplate` on `main` and is carried forward. Fixing it properly requires lexer-time segmentation (embedding boundary markers during `ScanTemplate` while escape/string context is available). Do NOT re-flag the naive brace counter in SplitTemplateAtBoundaries or ParseTaggedTemplate as a regression or new bug — it is intentionally deferred to a follow-up. The limitation exists because `ScanTemplate` stores the entire template (including `${...}` expressions) as a single flat token with no expression-level lexical analysis.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 222
File: scripts/test262_syntax_filter.py:0-0
Timestamp: 2026-04-08T15:47:44.282Z
Learning: In frostney/GocciaScript, `scripts/test262_syntax_filter.py` uses regex patterns to filter unsupported test262 constructs. The `loose_equality` pattern uses the conservative character-class approach (`[^!=<>]==[^=]`) which is accepted as intentional (pragmatic, non-blocking CI, negligible false-negative rate). The `loose_inequality` pattern was previously broken (`[^!]=!=[^=]` required a literal `=` before `!=`, so it never matched plain `a!=b`) and was fixed in commit 5a44c9b to `(?<!!)!=[^=]` — a negative lookbehind that correctly matches `!=` without matching `!==`. Do not suggest reverting the lookbehind-based `!=` pattern.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 267
File: units/Goccia.Lexer.pas:504-533
Timestamp: 2026-04-11T09:43:56.558Z
Learning: In frostney/GocciaScript (units/Goccia.Lexer.pas), the lexer intentionally throws on malformed escape sequences (\u{, \xG1, etc.) regardless of whether the template literal is tagged or untagged. Supporting cooked=undefined for tagged templates (per TC39 proposal-template-literal-revision / ES2018 Template Literal Revision) requires per-segment escape validity tracking and deferred error handling — this is explicitly deferred to a future PR. The raw string (RawSB) is always correctly captured for valid escapes. Do NOT re-flag missing cooked=undefined / malformed-escape tolerance in ScanTemplate or ProcessEscapeSequence as a bug in the current PR.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 258
File: units/Goccia.Lexer.pas:668-721
Timestamp: 2026-04-10T18:09:27.501Z
Learning: In frostney/GocciaScript (units/Goccia.Lexer.pas), the numeric literal scanner explicitly rejects `_` immediately after a decimal point (e.g., `1._5`) as a lex-time syntax error: the guard `(Peek = '.') and (PeekNext = '_')` raises TGocciaLexerError("Numeric separator must be between digits", SSuggestNumericSeparator) before the fraction-digit loop is entered. This matches ES2021 spec — `1._5` is NOT valid property access in this context; the lexer treats `.` as part of the numeric literal and rejects the adjacent underscore. The separate conservative guard `CharInSet(PeekNext, ['0'..'9'])` (which excludes `_`) is retained to allow `1._5` only after the explicit error check, but that branch is never reached when PeekNext is `_`.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 258
File: tests/language/expressions/numeric-separators.js:0-0
Timestamp: 2026-04-10T16:47:01.633Z
Learning: In frostney/GocciaScript (tests/language/expressions/numeric-separators.js and related lexer tests): Invalid numeric separator placement (trailing underscore, consecutive underscores, underscore adjacent to decimal point, underscore after radix prefix, underscore after exponent indicator) is rejected at lex time — before any JS token is produced. This means the JS test framework (TestRunner) cannot observe these parse failures from within a running test file. Negative parse-error cases for numeric separators must be verified externally via ScriptLoader/CLI invocation, not via `eval()`-based `expect(() => ...).toThrow()` patterns. Do not suggest eval-based negative tests for lex-time errors in this project.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 201
File: units/Goccia.AST.Expressions.pas:1339-1346
Timestamp: 2026-04-07T21:00:57.707Z
Learning: When reviewing GocciaScript coverage/branch tracking, do not require call-sites to explicitly pre-register binary (two-arm) branches before recording hits. In units/Goccia.Coverage.pas, TGocciaFileCoverage.RecordBranchHit automatically ensures the opposite arm exists via EnsureBranchExists(ALine, AColumn, 1 - ABranchIndex), establishing a zero-hit baseline for the paired arm whenever either arm is first recorded. Therefore, call-sites such as TGocciaConditionalExpression.Evaluate (Goccia.AST.Expressions.pas), EvaluateIf/EvaluateBinary/EvaluateSwitch (Goccia.Evaluator.pas), and OP_JUMP_IF_* handlers (Goccia.VM.pas) should not be flagged for missing a separate RegisterBranch call for binary branches—pairing is handled inside RecordBranchHit.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 201
File: units/Goccia.VM.pas:3547-3561
Timestamp: 2026-04-07T21:01:16.129Z
Learning: When reviewing calls that record branch coverage via `TGocciaFileCoverage.RecordBranchHit(ALine, AColumn, ABranchIndex)` for binary branches (branch indices 0 and 1), do not require an explicit prior `RegisterBranch(...)` at the call site. `RecordBranchHit` internally calls `EnsureBranchExists(ALine, AColumn, 1 - ABranchIndex)` for binary branches, which guarantees both arms exist in the branch list (the untaken arm is pre-registered with zero hits). Therefore VM/evaluator/AST branch recording sites in `Goccia.VM.pas`, `Goccia.Evaluator.pas`, and `Goccia.AST.Expressions.pas` should not be flagged for missing explicit pre-registration before `RecordBranchHit(...)`.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 192
File: tests/built-ins/Goccia/semver/satisfies.js:0-0
Timestamp: 2026-04-07T08:53:57.319Z
Learning: In frostney/GocciaScript, `Goccia.semver.satisfies` (and by extension the underlying semver implementation in `units/Goccia.Semver.pas`) returns `false` for malformed/invalid version inputs rather than throwing an error, matching node-semver semantics. When reviewing tests for `satisfies`, invalid-input assertions should use `toBe(false)`, not `toThrow`.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 51
File: units/Goccia.AST.Expressions.pas:1140-1157
Timestamp: 2026-03-08T03:22:02.868Z
Learning: In `units/Goccia.AST.Expressions.pas`, `TGocciaIncrementExpression.Evaluate`: the `TGocciaMemberExpression` branch only handles non-computed members (`PropertyName`). Computed member increment/decrement (e.g., `arr[i]++`) is not supported — `PropertyName` is empty for computed members, causing `GetProperty('')` to return `nil` and incorrectly triggering the error path. Tracked in GitHub issue `#52`. Do NOT flag this as a new bug in reviews; it is a known pre-existing limitation.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 53
File: souffle/Souffle.Wasm.Emitter.pas:0-0
Timestamp: 2026-03-08T04:50:29.540Z
Learning: In format.pas (GocciaScript formatter), ParseParamNames previously misidentified parenthesized arguments inside one-liner function bodies as parameter names, causing the `A` prefix to be prepended to them. The fix (f5b2f23) makes ParseParamNames ignore parentheses appearing after the first semicolon. Be cautious when reviewing one-liner procedure/function bodies in .pas files for incorrectly prefixed identifiers — this was the root cause of the AGC_ANY_CONVERT_EXTERN → AGC_ANY_CONVERT_EXTERN typos in souffle/Souffle.Wasm.Emitter.pas.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/ScopeMap.pas:0-0
Timestamp: 2026-03-17T22:18:02.668Z
Learning: Repo: frostney/GocciaScript — ScopeMap.pas (units/ScopeMap.pas) was deleted (commit d99cf96) after profiling showed TScopeMap's linear-scan IndexOf was ~2.7× slower than hash-based lookup (CreateBridgedContext samples rose from 24% to 51%). Scope bindings now use TOrderedStringMap<TLexicalBinding> (DJB2 hash, O(1) per level); scope chain walking is done via recursive TGocciaScope-level methods (Contains/GetLexicalBinding) rather than a parent-pointer chain inside the map. Do not flag TScopeMap or its Parent property in future reviews — the type no longer exists.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 107
File: units/Goccia.Runtime.Operations.pas:211-212
Timestamp: 2026-03-23T12:37:30.936Z
Learning: Repo: frostney/GocciaScript — units/Goccia.Runtime.Operations.pas (PR `#107`)
Learning: In TGocciaRuntimeOperations.MarkExternalRoots, entries in FModuleCache (cached module namespace/JSON records) must be traversed and their TSouffleValue references marked as external roots to survive GC. This behavior was added in commit 36f69cc; do not re‑flag missing FModuleCache rooting unless the logic regresses.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 107
File: units/ModuleResolver.pas:70-79
Timestamp: 2026-03-23T13:40:44.656Z
Learning: In frostney/GocciaScript, the alias prefix matching logic in `TModuleResolver.HasAlias` and `ApplyAliases` (units/ModuleResolver.pas) was moved verbatim from the original `Goccia.Modules.Resolver`. The partial-prefix behavior (e.g. `lib` can match `library/foo`) is intentionally preserved from the pre-existing implementation. Do not re-flag this as a bug unless a concrete functional regression is demonstrated; changing alias semantics is a separate concern tracked independently.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 147
File: units/Goccia.VM.pas:3042-3084
Timestamp: 2026-03-31T18:38:23.367Z
Learning: Repo: frostney/GocciaScript — units/Goccia.VM.pas (PR `#147`)
As of commit 457e712: (1) TGocciaVM.GetPropertyValue performs own-only private static accessor lookup on '#…' for TGocciaClassValue and throws TypeError on class-brand misses; (2) TGocciaVM.SetPropertyValue avoids inherited private accessor resolution, but still allows raw private static writes via SetRawPrivateValue to support current class initialization that emits OP_SET_PROP_CONST for private static fields. Planned follow-up is to add dedicated opcodes to define private static fields at class-declaration time and then make SetPropertyValue strict (throw on brand miss with no fallback). Do not re-flag the current fallback until those opcodes exist.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-08T04:38:51.694Z
Learning: Applies to **/*.pas : Use split constant units for runtime constants instead of hardcoded string literals: `Goccia.Constants.PropertyNames` for property names, `Goccia.Constants.TypeNames` for type names, `Goccia.Constants.ErrorNames` for error names, `Goccia.Constants.ConstructorNames` for constructor names, and `Goccia.Constants.SymbolNames` for symbol names.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T22:00:59.812Z
Learning: Applies to units/**/*.pas : `type`/`interface` declarations and `import type`/`export type` must produce `TGocciaEmptyStatement` (no-op at runtime). Access modifiers (`public`, `protected`, `private`, `readonly`, `override`, `abstract`) in class bodies must be consumed and discarded.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 286
File: units/Goccia.Spec.pas:30-51
Timestamp: 2026-04-12T15:33:50.823Z
Learning: In frostney/GocciaScript, `Proxy` is fully supported — it was implemented in PR `#226`. The `Proxy` entry in `ES2015_FEATURES` in `units/Goccia.Spec.pas` is correct and intentional. Do not flag `Proxy` as unimplemented in future reviews.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 110
File: units/Goccia.Runtime.Operations.pas:0-0
Timestamp: 2026-03-23T11:25:00.381Z
Learning: In this repo’s GocciaScript Pascal code, when implementing thenable detection/callability checks (e.g., around Await/unwrap logic), always read the thenable’s `then` property using `Goccia.Constants.PropertyNames.PROP_THEN` rather than a string literal. If the value being inspected is a `TSouffleRecord`, unwrap the retrieved `then` property via `UnwrapToGocciaValue` before testing whether it is callable (e.g., in `TGocciaRuntimeOperations.AwaitValue`).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 276
File: tests/built-ins/Goccia/build.js:6-7
Timestamp: 2026-04-11T18:29:30.289Z
Learning: In frostney/GocciaScript, the pattern `const hasGoccia = typeof Goccia !== "undefined"` combined with `describe.runIf(hasGoccia)(...)` is intentional and used across the entire `tests/` suite. It allows test files to run safely in non-GocciaScript environments (e.g., Vitest compatibility checks) where GocciaScript-specific globals like `Goccia` are not present. Do NOT flag this conditional-skip guard as a regression risk or suggest making suites unconditional — the portability requirement is by design.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 53
File: souffle/Souffle.Wasm.Emitter.pas:1467-1477
Timestamp: 2026-03-09T17:21:38.193Z
Learning: In this codebase (Frostney/GocciaScript), for Pascal/Delphi (FPC) files using AnsiString with {$mode delphi} {H+}, treat String as byte-indexed. Length(str) returns byte count and Byte(str[I]) is a no-op cast. Do not replace per-byte copy loops like Data[I-1] := Byte(AStr[I]) with TEncoding.GetBytes, since TEncoding is a Delphi RTL feature unavailable in FPC's SysUtils and would break the build. Apply this guidance to all .pas files under the project; ensure manual per-byte copying logic remains correct for UTF-8 content encoded in AnsiString, and avoid introducing RTL dependencies not supported by FPC.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 58
File: units/Goccia.Runtime.Operations.pas:5959-6132
Timestamp: 2026-03-10T11:17:46.617Z
Learning: GocciaScript’s Promise statics are intentionally not species/subclass-aware. Interpreter-level implementations (PromiseAll, PromiseRace, PromiseAllSettled, PromiseAny, withResolvers, try, resolve, reject) construct plain TGocciaPromiseValue via WrapAsPromise and do not use the call receiver (AThisValue). The Souffle VM bridge mirrors this by not forwarding AReceiver and by constructing TGocciaPromiseValue directly where applicable. Do not flag missing receiver-based construction for Promise statics. This applies to units/Goccia.Runtime.Operations.pas and related Promise implementations; ensure consistency across related Pas files in the frostney/GocciaScript repository.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 63
File: units/Goccia.Runtime.Operations.pas:0-0
Timestamp: 2026-03-11T01:50:11.071Z
Learning: Replace hardcoded string literals 'now', 'toJSON', and 'timeOrigin' with the new constants from Goccia.Constants.PropertyNames (PROP_NOW, PROP_TO_JSON, PROP_TIME_ORIGIN). Apply this refactor across the repository wherever these literals are used, including but not limited to units/Goccia.Runtime.Operations.pas and units/Goccia.Builtins.Performance.pas. Use a project-wide search (e.g., regex or code navigation) to identify all occurrences, update to the respective constants, and adjust imports or uses accordingly. Ensure all references compile and behavior remains unchanged.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 63
File: units/Goccia.Runtime.Operations.pas:7514-7544
Timestamp: 2026-03-11T08:22:20.482Z
Learning: When bridging a native namespace object to a TSouffleRecord (e.g., performance) in units/Goccia.Runtime.Operations.pas, always set Result.Delegate := ARuntime.VM.RecordDelegate after TSouffleRecord.Create(...). This ensures Object.prototype methods (toString/hasOwnProperty/valueOf/toLocaleString) work in bytecode mode and NativeRecordToString honors Symbol.toStringTag. Apply this pattern to BuildPerformanceRecord and any future bridged records.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/HashMap.pas:266-309
Timestamp: 2026-03-17T22:17:37.410Z
Learning: Do not flag enumeration in THashMap<TKey, TValue> as concurrent-modification unsafe. The no-mutation contract is maintained during enumeration: GetEnumerator shares the internal FSlots array (no per-call allocation) and only mutation occurs via destructor or Clear, not during active iteration. This pattern should be recognized across similar Pascal units/pascal collection implementations; when reviewing code, ensure no concurrent-modification warnings are raised for shared internal structures during enumeration if the no-mutation contract is preserved.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/OrderedMap.pas:0-0
Timestamp: 2026-03-17T22:17:55.588Z
Learning: Enforce the constraint on TOrderedMap<TKey, TValue> in units/OrderedMap.pas: TKey must be an unmanaged, fixed-size value type (no strings, dynamic arrays, or records with managed/pointer fields). HashKey should use fast paths for SizeOf(TKey)=8 (QWord) and SizeOf(TKey)=4 (Cardinal); KeysEqual should compare via direct PQWord/PCardinal for those sizes. The raw-byte DJB2/CompareMem fallback is dead code for current uses. Document this constraint in a unit-header comment as the enforcement mechanism. Do NOT re-flag the byte-wise hash/equality in future reviews as long as all TOrderedMap specializations use pointer-sized unmanaged keys.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/ScopeMap.pas:0-0
Timestamp: 2026-03-17T22:18:00.984Z
Learning: Do not re-introduce TScopeMap. In frostney/GocciaScript, TScopeMap<TValue> was removed due to performance regressions from its linear-scan Add-as-upsert behavior. Reviews should flag any use of TScopeMap in Units/ScopeMap.pas or elsewhere and ensure bindings use TOrderedStringMap<TLexicalBinding> instead. If reviewing similar Pascal files, prefer avoiding upsert-like add semantics that overwrite existing entries, as they correlated with bytecode performance drops. Ensure changes adhere to the established replacement: TOrderedStringMap<TLexicalBinding>.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/BaseMap.pas:22-22
Timestamp: 2026-03-18T08:12:24.165Z
Learning: Shared/utility units under the repository (e.g., units/BaseMap.pas, HashMap.pas, OrderedMap.pas, etc.) that are used across multiple parts of the project should not be required to use the Goccia.* prefix. A separate naming convention will be defined in a follow-up issue. Do not flag missing Goccia.* prefix for these shared infrastructure units in reviews.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/BaseMap.pas:22-22
Timestamp: 2026-03-18T08:12:24.165Z
Learning: Do not flag missing Goccia.* prefixes for shared/utility units that are used across multiple parts of the project or can be extracted as standalone infrastructure (e.g., BaseMap.pas, HashMap.pas, OrderedMap.pas, OrderedStringMap.pas, GC, test assertion units). A separate naming convention (to be defined in issue `#104`) should apply to these targets. When reviewing, apply this guideline to all similar shared utility units under the units directory; for one-off implementations outside this scope, continue with the existing naming expectations.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 107
File: units/Goccia.Runtime.Operations.pas:4349-4353
Timestamp: 2026-03-23T12:38:54.412Z
Learning: In Pascal source under units/, when decoding JSON string escape sequences that represent Unicode code points and then converting them to UTF-8, prefer using the existing helper (implemented via CodePointToUTF8) that correctly combines UTF-16 surrogate pairs (\uD800–\uDBFF + \uDC00–\uDFFF) into a single Unicode code point before UTF-8 encoding. Avoid ad-hoc conversions like Char(StrToInt(...)) for code-point→UTF-8 conversion logic; use the helper instead to ensure surrogate pairs are handled correctly.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 109
File: units/Goccia.Runtime.Operations.pas:6337-6344
Timestamp: 2026-03-23T15:57:55.802Z
Learning: In Pascal unit files under units/, any iterator result record objects produced by native iterators (e.g., in routines analogous to NativeMapIteratorNext / NativeSetIteratorNext) must: (1) set `Rec.Delegate := FVM.RecordDelegate`; and (2) set properties using `PROP_VALUE` / `PROP_DONE` constants from `Goccia.Constants.PropertyNames` rather than hard-coded string literals. Apply the same pattern to any future native iterator result objects so they interoperate correctly in bytecode mode (e.g., for `Object.prototype` method behavior) and keep property-name usage consistent via the shared constants.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 136
File: docs/adding-built-in-types.md:105-133
Timestamp: 2026-03-27T14:09:25.371Z
Learning: In frostney/GocciaScript Pascal code, do not flag or suggest refactoring the early-exit singleton initialization guard `if Assigned(FShared) then Exit` when it appears at the top of `InitializePrototype`. The project documentation treats this early-exit form as the canonical/shared-prototype singleton initialization template (i.e., avoid recommending an `if not Assigned(FShared) then ...` wrapping alternative).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 189
File: units/Goccia.JSON.pas:499-526
Timestamp: 2026-04-05T17:33:32.709Z
Learning: In frostney/GocciaScript, when using `TStringBuffer` from the `StringBuffer` unit, treat it as an advanced record (stack-allocated) with automatic cleanup by the compiler. For `TStringBuffer` locals, do not require or flag missing `SB.Free` calls in `try/finally` blocks—no destructor/free call is needed or appropriate. Only look for `Free` when the variable is actually a heap-allocated class instance.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 189
File: units/Goccia.JSON.pas:535-597
Timestamp: 2026-04-05T17:33:35.381Z
Learning: When reviewing Free Pascal code in this repo, treat `TStringBuffer` (from the `StringBuffer` unit) as an advanced record (value type), meaning instances are stack-allocated. Do not report or require `SB.Free` / `SB.FreeAndNil` (or any `Free` call) for variables typed as `TStringBuffer`, and do not treat an empty or no-op `finally` block after `TStringBuffer` usage as an issue. Only expect heap-management calls for actual `class`/reference types; calling `Free` on an advanced record is incorrect.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 200
File: REPL.dpr:0-0
Timestamp: 2026-04-07T17:12:07.324Z
Learning: When reviewing timing/metrics code in the GocciaScript repo (e.g., REPL.dpr, ScriptLoader, TestRunner, BenchmarkRunner, and TGocciaEngine.Execute), follow the established timing convention: capture `StartTime` before the JSX pre-pass (`TGocciaJSXTransformer.Transform`) and `StringListToLFText`, then capture `LexEnd` after `ScanTokens`. The `Lex` duration (`LexEnd - StartTime`) is intended to include the JSX pre-pass because it is treated as part of the source→tokens pipeline. Do not flag this ordering as a bug and do not recommend moving `StartTime` to after the JSX transform, as it would break the bucket semantics.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 209
File: units/Goccia.FFI.DynamicLibrary.pas:7-21
Timestamp: 2026-04-08T13:56:59.262Z
Learning: In Free Pascal (FPC), `TLibHandle` is defined in the `System` unit (`TLibHandle = PtrInt`), not in `Dynlibs`. Since `System` is implicitly available in all Pascal units, referencing `TLibHandle` in the interface section (e.g., as a private field type of a class declared in the interface/`interface` section) does not require `Dynlibs` to be listed in the interface `uses` clause. Only ensure `Dynlibs` is included in the implementation `uses` clause when you actually use `LoadLibrary`, `GetProcAddress`, or `FreeLibrary`. Therefore, do not flag a missing `Dynlibs` entry in the interface `uses` clause when the interface section references only `TLibHandle`.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 242
File: AGENTS.md:396-400
Timestamp: 2026-04-09T11:50:01.452Z
Learning: In .pas code, avoid unsafe Int64→Double conversions/workarounds that trigger FPC 3.2.2 miscompilations. Specifically, do not rely on `Double(Int64Var)` when it performs bit reinterpretation (Delphi-mode front-end bug on affected versions/platforms). Also do not use arithmetic patterns like `Int64Var * 1.0` / `Int64Var * Double` that can produce incorrect results near ±2^31 on AArch64. Prefer value-safe conversions via implicit assignment (e.g., `var D: Double; D := SomeInt64`) or implicit promotion through function/constructor parameter passing (e.g., `TGocciaNumberLiteralValue.Create(SomeInt64)`).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 253
File: units/Goccia.Values.Iterator.Concat.pas:84-88
Timestamp: 2026-04-10T12:44:59.067Z
Learning: In frostney/GocciaScript (GocciaScript), `TGocciaFunctionBase(callable).Call(args, thisValue)` is the project-standard way to invoke callable values. During code review, do not flag `TGocciaFunctionBase(...)` casts at call sites as unsafe and do not recommend replacing them with an alternative “generic callable path”—for this codebase, this cast-and-`Call` pattern is the intended generic callable invocation mechanism.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 273
File: units/Goccia.Values.URLValue.pas:465-470
Timestamp: 2026-04-11T17:42:27.330Z
Learning: In this codebase, when implementing or updating a `TGCManagedObject.MarkReferences` override for a held reference, treat `MarkReferences` as the act of marking *and* tracing children. The expected pattern is: after a nil check, call the held reference’s `MarkReferences` implementation directly (e.g., `TGocciaSomeValue(FRef).MarkReferences`) rather than invoking any non-existent separate `Mark(obj)`/`Mark()` helper. Do not suggest “call `Mark(obj)`”; this project’s marking flow starts by setting `FGCMark := FCurrentMark` and then tracing child references from within `MarkReferences` (consistent with `TGocciaMapValue`, `TGocciaSetValue`, `TGocciaArrayValue`, etc.).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 271
File: units/Goccia.Builtins.GlobalObject.pas:104-129
Timestamp: 2026-04-11T19:43:06.627Z
Learning: In frostney/GocciaScript, `TGarbageCollector.AddTempRoot`/`RemoveTempRoot` are set-membership (not reference-counted). In built-in method implementations, ONLY call `AddTempRoot`/`RemoveTempRoot` if the value was freshly boxed by `ToObject` (i.e., the argument was not already a `TGocciaObjectValue`). Do not unconditionally add/remove temp roots for values passed in as method arguments, since a single `RemoveTempRoot(Obj)` will remove the root for all holders and can cause use-after-free on the next GC cycle. Pattern: `if Assigned(TGarbageCollector.Instance) and not (AArgs.GetElement(0) is TGocciaObjectValue) then AddTempRoot(Obj); try ... finally if Assigned(TGarbageCollector.Instance) and not (AArgs.GetElement(0) is TGocciaObjectValue) then RemoveTempRoot(Obj); end;`

Learnt from: frostney
Repo: frostney/GocciaScript PR: 278
File: units/Goccia.Parser.pas:1071-1111
Timestamp: 2026-04-12T07:39:20.695Z
Learning: Do not free the result of `TGocciaLexer.ScanTokens` (e.g., in `ParseInterpolationExpression` and any other caller). `ScanTokens` returns a reference to the lexer's internal `FTokens: TObjectList<TGocciaToken>` created with `OwnsObjects=True` and freed by `TGocciaLexer`’s destructor. Adding `Tokens.Free` (or otherwise freeing the returned list/object) will cause double-free/access violations; token lifetime is managed entirely by the `TGocciaLexer` instance.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 285
File: units/Goccia.Lexer.pas:207-213
Timestamp: 2026-04-12T15:46:28.683Z
Learning: In this Frostney/GocciaScript project (Free Pascal / FPC, including files under units/), do not recommend adding explicit `{$B-}` directives (either inside units or in `Goccia.inc`) to “fix” short-circuit evaluation safety. `{$B-}` is already FPC’s default in all compiler modes (`fpc`, `delphi`, `objfpc`). Treat missing `{$B-}` as expected and do not flag it as a maintainability or safety issue. Only note `{$B-}` overrides if an explicit `{$B+}` is present in the file or via a highly visible project-level `fpc.cfg` setting.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 12, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 5275 5275
Tests Passed 5234 ✅ 5275 ✅
Tests Skipped 41 0
Tests Test Duration 385.4ms 332.8ms
Tests Lex 104.9ms 62.8ms
Tests Parse 136.1ms 129.5ms
Tests Compile 76.2ms
Tests Execute 403.6ms 362.9ms
Tests Engine Total 644.6ms 631.4ms
Benchmarks Total 364 364
Benchmarks Duration 9.88min 8.30min

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 12, 2026

Benchmark Results

364 benchmarks

Interpreted: 🟢 38 improved · 🔴 251 regressed · 75 unchanged · avg -3.2%
Bytecode: 🟢 241 improved · 🔴 34 regressed · 89 unchanged · avg +3.6%

arraybuffer.js — Interp: 🟢 1, 🔴 13 · avg -7.1% · Bytecode: 🟢 5, 9 unch. · avg +1.0%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 508,312 ops/sec [505,700..511,265] → 454,076 ops/sec [444,394..456,781] 🔴 -10.7% 517,946 ops/sec [513,744..522,750] → 521,412 ops/sec [516,974..525,031] ~ overlap (+0.7%)
create ArrayBuffer(64) 488,404 ops/sec [485,040..489,388] → 431,483 ops/sec [430,938..434,182] 🔴 -11.7% 492,462 ops/sec [491,112..497,773] → 494,783 ops/sec [492,786..495,679] ~ overlap (+0.5%)
create ArrayBuffer(1024) 351,897 ops/sec [351,690..353,236] → 339,422 ops/sec [333,916..342,205] 🔴 -3.5% 374,282 ops/sec [371,742..375,534] → 379,653 ops/sec [375,731..382,261] 🟢 +1.4%
create ArrayBuffer(8192) 142,601 ops/sec [140,909..143,274] → 152,600 ops/sec [150,764..152,801] 🟢 +7.0% 161,942 ops/sec [161,213..163,781] → 163,699 ops/sec [159,787..165,148] ~ overlap (+1.1%)
slice full buffer (64 bytes) 540,402 ops/sec [537,258..542,705] → 480,734 ops/sec [479,571..482,219] 🔴 -11.0% 607,798 ops/sec [601,558..613,884] → 612,845 ops/sec [595,336..616,283] ~ overlap (+0.8%)
slice half buffer (512 of 1024 bytes) 457,817 ops/sec [456,217..459,423] → 420,488 ops/sec [419,480..422,668] 🔴 -8.2% 527,319 ops/sec [523,577..529,120] → 526,938 ops/sec [525,538..528,035] ~ overlap (-0.1%)
slice with negative indices 451,880 ops/sec [449,432..453,312] → 410,434 ops/sec [408,688..413,220] 🔴 -9.2% 568,601 ops/sec [567,754..569,776] → 566,491 ops/sec [558,048..568,148] ~ overlap (-0.4%)
slice empty range 515,015 ops/sec [513,718..516,643] → 466,263 ops/sec [463,761..467,763] 🔴 -9.5% 599,608 ops/sec [595,042..603,664] → 603,207 ops/sec [600,966..604,675] ~ overlap (+0.6%)
byteLength access 1,450,574 ops/sec [1,446,713..1,454,226] → 1,321,709 ops/sec [1,316,381..1,327,227] 🔴 -8.9% 1,780,276 ops/sec [1,758,084..1,806,816] → 1,766,245 ops/sec [1,655,386..1,770,999] ~ overlap (-0.8%)
Symbol.toStringTag access 1,084,052 ops/sec [1,069,500..1,085,467] → 1,011,574 ops/sec [1,009,591..1,019,821] 🔴 -6.7% 1,071,280 ops/sec [1,058,782..1,073,389] → 1,095,050 ops/sec [1,092,748..1,095,845] 🟢 +2.2%
ArrayBuffer.isView 805,893 ops/sec [787,054..806,987] → 707,033 ops/sec [704,281..710,080] 🔴 -12.3% 851,556 ops/sec [837,817..857,170] → 865,180 ops/sec [863,359..871,646] 🟢 +1.6%
clone ArrayBuffer(64) 437,510 ops/sec [434,908..439,117] → 414,320 ops/sec [413,809..415,478] 🔴 -5.3% 489,588 ops/sec [487,040..491,179] → 511,764 ops/sec [508,090..513,024] 🟢 +4.5%
clone ArrayBuffer(1024) 334,593 ops/sec [330,868..335,272] → 324,065 ops/sec [319,729..325,158] 🔴 -3.1% 372,705 ops/sec [371,887..376,368] → 375,979 ops/sec [374,820..377,854] ~ overlap (+0.9%)
clone ArrayBuffer inside object 286,997 ops/sec [285,482..289,383] → 266,573 ops/sec [262,018..267,392] 🔴 -7.1% 300,884 ops/sec [299,522..302,175] → 304,693 ops/sec [303,816..305,340] 🟢 +1.3%
arrays.js — Interp: 🔴 19 · avg -7.1% · Bytecode: 🟢 6, 🔴 8, 5 unch. · avg +0.3%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 14,213 ops/sec [14,122..14,261] → 12,841 ops/sec [12,781..12,882] 🔴 -9.7% 17,214 ops/sec [17,141..17,344] → 16,531 ops/sec [16,358..16,571] 🔴 -4.0%
Array.from 10 elements 235,503 ops/sec [235,323..236,440] → 220,951 ops/sec [219,736..221,387] 🔴 -6.2% 235,337 ops/sec [186,560..236,361] → 242,413 ops/sec [241,479..245,003] 🟢 +3.0%
Array.of 10 elements 295,473 ops/sec [294,761..296,751] → 273,879 ops/sec [272,667..275,762] 🔴 -7.3% 301,848 ops/sec [300,688..304,495] → 306,829 ops/sec [305,953..307,935] 🟢 +1.7%
spread into new array 357,837 ops/sec [355,185..359,947] → 331,197 ops/sec [328,733..332,820] 🔴 -7.4% 193,275 ops/sec [192,592..194,253] → 201,379 ops/sec [199,998..202,529] 🟢 +4.2%
map over 50 elements 26,170 ops/sec [25,993..26,302] → 24,863 ops/sec [24,721..25,022] 🔴 -5.0% 33,055 ops/sec [32,884..33,340] → 33,690 ops/sec [33,647..33,774] 🟢 +1.9%
filter over 50 elements 24,111 ops/sec [24,001..24,183] → 22,461 ops/sec [22,403..22,574] 🔴 -6.8% 30,195 ops/sec [28,914..30,558] → 30,761 ops/sec [30,446..31,130] ~ overlap (+1.9%)
reduce sum 50 elements 27,404 ops/sec [27,256..27,552] → 25,234 ops/sec [25,145..25,298] 🔴 -7.9% 31,978 ops/sec [31,113..32,183] → 31,833 ops/sec [31,587..32,069] ~ overlap (-0.5%)
forEach over 50 elements 24,661 ops/sec [24,491..24,807] → 22,564 ops/sec [22,389..22,604] 🔴 -8.5% 33,055 ops/sec [32,927..33,215] → 32,577 ops/sec [32,477..32,631] 🔴 -1.4%
find in 50 elements 34,506 ops/sec [34,403..34,941] → 32,301 ops/sec [32,170..32,423] 🔴 -6.4% 46,497 ops/sec [46,264..46,707] → 47,625 ops/sec [47,519..47,717] 🟢 +2.4%
sort 20 elements 13,146 ops/sec [13,071..13,221] → 12,287 ops/sec [12,248..12,308] 🔴 -6.5% 17,812 ops/sec [17,762..17,860] → 17,213 ops/sec [17,177..17,241] 🔴 -3.4%
flat nested array 124,191 ops/sec [123,406..125,139] → 116,363 ops/sec [116,302..116,759] 🔴 -6.3% 122,691 ops/sec [122,370..123,765] → 132,716 ops/sec [130,573..133,083] 🟢 +8.2%
flatMap 80,008 ops/sec [79,543..80,313] → 72,444 ops/sec [72,172..72,764] 🔴 -9.5% 88,541 ops/sec [87,749..88,990] → 89,009 ops/sec [86,567..90,422] ~ overlap (+0.5%)
map inside map (5x5) 21,730 ops/sec [21,556..21,808] → 20,049 ops/sec [20,007..20,098] 🔴 -7.7% 24,867 ops/sec [24,746..25,067] → 24,714 ops/sec [24,663..24,729] 🔴 -0.6%
filter inside map (5x10) 16,564 ops/sec [16,492..16,605] → 15,243 ops/sec [15,204..15,310] 🔴 -8.0% 19,995 ops/sec [19,982..20,056] → 19,613 ops/sec [19,586..19,729] 🔴 -1.9%
reduce inside map (5x10) 20,117 ops/sec [20,099..20,178] → 18,518 ops/sec [18,399..18,548] 🔴 -7.9% 23,831 ops/sec [23,668..23,967] → 23,248 ops/sec [23,108..23,345] 🔴 -2.4%
forEach inside forEach (5x10) 17,672 ops/sec [17,594..17,756] → 16,394 ops/sec [16,326..16,455] 🔴 -7.2% 25,702 ops/sec [25,545..25,847] → 24,708 ops/sec [24,505..24,823] 🔴 -3.9%
find inside some (10x10) 14,003 ops/sec [13,929..14,060] → 13,090 ops/sec [13,075..13,116] 🔴 -6.5% 18,033 ops/sec [18,002..18,101] → 17,993 ops/sec [17,814..18,005] ~ overlap (-0.2%)
map+filter chain nested (5x20) 5,406 ops/sec [5,392..5,426] → 5,085 ops/sec [5,061..5,112] 🔴 -6.0% 6,853 ops/sec [6,810..6,884] → 6,715 ops/sec [6,699..6,731] 🔴 -2.0%
reduce flatten (10x5) 40,010 ops/sec [39,579..40,607] → 38,615 ops/sec [38,459..38,819] 🔴 -3.5% 17,621 ops/sec [17,556..17,851] → 17,907 ops/sec [17,817..18,024] ~ overlap (+1.6%)
async-await.js — Interp: 🔴 6 · avg -7.9% · Bytecode: 🟢 4, 2 unch. · avg +2.3%
Benchmark Interpreted Δ Bytecode Δ
single await 415,734 ops/sec [414,775..416,626] → 374,706 ops/sec [374,477..375,250] 🔴 -9.9% 404,608 ops/sec [400,190..406,663] → 418,127 ops/sec [416,536..420,594] 🟢 +3.3%
multiple awaits 189,068 ops/sec [188,573..190,035] → 167,764 ops/sec [166,606..168,364] 🔴 -11.3% 173,401 ops/sec [171,531..174,088] → 178,238 ops/sec [177,211..178,638] 🟢 +2.8%
await non-Promise value 918,520 ops/sec [914,467..935,973] → 874,665 ops/sec [870,741..876,034] 🔴 -4.8% 1,223,911 ops/sec [1,220,436..1,231,402] → 1,227,446 ops/sec [1,218,359..1,232,004] ~ overlap (+0.3%)
await with try/catch 398,592 ops/sec [397,648..400,812] → 369,690 ops/sec [369,279..370,400] 🔴 -7.3% 402,310 ops/sec [401,113..406,204] → 414,287 ops/sec [413,119..416,359] 🟢 +3.0%
await Promise.all 54,400 ops/sec [53,922..54,650] → 49,829 ops/sec [49,648..50,193] 🔴 -8.4% 50,982 ops/sec [50,474..51,406] → 51,462 ops/sec [51,151..51,733] ~ overlap (+0.9%)
nested async function call 208,052 ops/sec [207,683..208,591] → 195,593 ops/sec [195,258..196,798] 🔴 -6.0% 233,183 ops/sec [232,881..236,263] → 241,175 ops/sec [239,984..241,940] 🟢 +3.4%
base64.js — Interp: 🔴 9, 1 unch. · avg -7.9% · Bytecode: 🟢 8, 2 unch. · avg +2.0%
Benchmark Interpreted Δ Bytecode Δ
short ASCII (13 chars) 553,147 ops/sec [552,565..554,602] → 505,003 ops/sec [496,596..518,552] 🔴 -8.7% 624,537 ops/sec [620,773..628,260] → 631,473 ops/sec [628,193..635,143] ~ overlap (+1.1%)
medium ASCII (450 chars) 145,928 ops/sec [143,169..146,462] → 145,897 ops/sec [145,218..146,562] ~ overlap (-0.0%) 155,771 ops/sec [154,802..155,998] → 158,442 ops/sec [158,215..158,602] 🟢 +1.7%
Latin-1 characters 578,331 ops/sec [577,016..580,429] → 528,016 ops/sec [514,033..536,163] 🔴 -8.7% 641,637 ops/sec [638,616..642,384] → 646,724 ops/sec [642,505..650,402] 🟢 +0.8%
short base64 (20 chars) 420,654 ops/sec [415,523..424,040] → 377,032 ops/sec [369,634..379,759] 🔴 -10.4% 432,144 ops/sec [431,232..433,950] → 444,197 ops/sec [439,650..445,315] 🟢 +2.8%
medium base64 (600 chars) 88,849 ops/sec [88,481..89,047] → 82,591 ops/sec [82,308..82,839] 🔴 -7.0% 85,728 ops/sec [84,719..86,244] → 86,711 ops/sec [85,601..88,599] ~ overlap (+1.1%)
Latin-1 output 438,094 ops/sec [437,041..445,851] → 397,534 ops/sec [392,682..401,402] 🔴 -9.3% 455,873 ops/sec [449,806..458,575] → 475,353 ops/sec [474,074..476,285] 🟢 +4.3%
forgiving (no padding) 478,287 ops/sec [473,523..490,616] → 419,880 ops/sec [417,559..427,874] 🔴 -12.2% 435,798 ops/sec [434,754..437,826] → 451,024 ops/sec [449,951..453,279] 🟢 +3.5%
with whitespace 432,049 ops/sec [427,823..439,652] → 387,180 ops/sec [380,890..396,175] 🔴 -10.4% 403,412 ops/sec [401,355..405,518] → 411,030 ops/sec [409,964..413,019] 🟢 +1.9%
atob(btoa(short)) 276,211 ops/sec [272,456..276,944] → 249,409 ops/sec [246,883..251,668] 🔴 -9.7% 270,684 ops/sec [268,830..271,158] → 274,007 ops/sec [271,237..275,039] 🟢 +1.2%
atob(btoa(medium)) 55,795 ops/sec [55,683..55,995] → 54,088 ops/sec [53,996..54,372] 🔴 -3.1% 55,618 ops/sec [55,374..55,858] → 56,501 ops/sec [56,229..56,652] 🟢 +1.6%
classes.js — Interp: 🟢 3, 🔴 24, 4 unch. · avg -4.3% · Bytecode: 🟢 11, 🔴 3, 17 unch. · avg +3.7%
Benchmark Interpreted Δ Bytecode Δ
simple class new 147,637 ops/sec [146,671..148,322] → 139,460 ops/sec [138,985..139,899] 🔴 -5.5% 208,244 ops/sec [207,571..209,241] → 210,139 ops/sec [209,347..210,712] 🟢 +0.9%
class with defaults 118,641 ops/sec [117,585..119,098] → 109,542 ops/sec [108,879..110,609] 🔴 -7.7% 140,073 ops/sec [138,464..141,649] → 141,804 ops/sec [140,921..142,421] ~ overlap (+1.2%)
50 instances via Array.from 6,162 ops/sec [6,134..6,182] → 5,608 ops/sec [5,602..5,629] 🔴 -9.0% 8,704 ops/sec [8,662..8,707] → 8,463 ops/sec [8,437..8,508] 🔴 -2.8%
instance method call 76,971 ops/sec [76,398..77,333] → 70,480 ops/sec [70,229..70,990] 🔴 -8.4% 98,905 ops/sec [98,123..99,357] → 99,605 ops/sec [98,786..99,873] ~ overlap (+0.7%)
static method call 120,852 ops/sec [119,693..121,212] → 112,548 ops/sec [110,707..113,173] 🔴 -6.9% 191,591 ops/sec [190,274..192,253] → 193,428 ops/sec [193,284..193,585] 🟢 +1.0%
single-level inheritance 60,083 ops/sec [59,735..60,307] → 55,391 ops/sec [55,263..55,552] 🔴 -7.8% 73,338 ops/sec [72,183..73,966] → 74,089 ops/sec [73,336..74,863] ~ overlap (+1.0%)
two-level inheritance 51,275 ops/sec [50,956..51,588] → 47,722 ops/sec [47,534..47,957] 🔴 -6.9% 59,362 ops/sec [57,676..60,697] → 60,155 ops/sec [58,266..61,260] ~ overlap (+1.3%)
private field access 78,322 ops/sec [78,067..78,458] → 71,298 ops/sec [71,001..71,538] 🔴 -9.0% 97,191 ops/sec [96,849..98,038] → 94,940 ops/sec [94,728..95,736] 🔴 -2.3%
private methods 85,008 ops/sec [84,856..85,452] → 78,288 ops/sec [78,163..78,481] 🔴 -7.9% 98,741 ops/sec [97,215..99,654] → 99,241 ops/sec [99,171..100,338] ~ overlap (+0.5%)
getter/setter access 81,055 ops/sec [80,784..81,364] → 75,682 ops/sec [75,533..76,005] 🔴 -6.6% 110,506 ops/sec [109,576..111,299] → 106,741 ops/sec [106,170..107,088] 🔴 -3.4%
class decorator (identity) 99,103 ops/sec [97,545..100,248] → 93,430 ops/sec [92,141..93,708] 🔴 -5.7% 103,515 ops/sec [103,281..104,654] → 109,343 ops/sec [107,568..111,504] 🟢 +5.6%
class decorator (wrapping) 57,800 ops/sec [56,773..58,211] → 55,744 ops/sec [55,424..56,071] 🔴 -3.6% 59,606 ops/sec [56,462..62,147] → 59,102 ops/sec [58,789..60,713] ~ overlap (-0.8%)
identity method decorator 69,506 ops/sec [69,008..69,601] → 65,998 ops/sec [65,840..66,775] 🔴 -5.0% 87,173 ops/sec [81,057..94,058] → 91,428 ops/sec [86,047..97,203] ~ overlap (+4.9%)
wrapping method decorator 55,867 ops/sec [55,743..56,369] → 52,676 ops/sec [52,592..52,851] 🔴 -5.7% 61,809 ops/sec [58,532..67,219] → 67,944 ops/sec [62,558..73,198] ~ overlap (+9.9%)
stacked method decorators (x3) 36,646 ops/sec [36,297..36,838] → 35,402 ops/sec [35,283..35,596] 🔴 -3.4% 41,456 ops/sec [39,087..44,463] → 43,700 ops/sec [41,839..46,891] ~ overlap (+5.4%)
identity field decorator 75,218 ops/sec [74,787..75,338] → 72,548 ops/sec [71,797..73,414] 🔴 -3.5% 72,543 ops/sec [70,558..75,987] → 77,727 ops/sec [74,513..80,108] ~ overlap (+7.1%)
field initializer decorator 62,469 ops/sec [62,245..63,023] → 60,178 ops/sec [60,031..60,695] 🔴 -3.7% 63,482 ops/sec [61,067..67,508] → 66,731 ops/sec [64,298..69,110] ~ overlap (+5.1%)
getter decorator (identity) 69,557 ops/sec [69,200..69,972] → 65,971 ops/sec [65,735..66,554] 🔴 -5.2% 73,702 ops/sec [71,973..74,905] → 76,001 ops/sec [75,373..77,074] 🟢 +3.1%
setter decorator (identity) 58,125 ops/sec [57,822..58,620] → 55,401 ops/sec [55,217..55,645] 🔴 -4.7% 58,756 ops/sec [58,110..59,149] → 62,238 ops/sec [61,878..62,558] 🟢 +5.9%
static method decorator 73,965 ops/sec [73,737..74,361] → 71,521 ops/sec [71,405..71,875] 🔴 -3.3% 86,799 ops/sec [81,505..91,101] → 84,807 ops/sec [83,152..90,598] ~ overlap (-2.3%)
static field decorator 86,477 ops/sec [85,519..86,596] → 83,118 ops/sec [82,284..83,700] 🔴 -3.9% 81,090 ops/sec [78,718..85,972] → 86,542 ops/sec [83,973..93,356] ~ overlap (+6.7%)
private method decorator 56,020 ops/sec [51,015..56,541] → 53,741 ops/sec [53,591..53,914] ~ overlap (-4.1%) 82,150 ops/sec [76,983..89,151] → 78,942 ops/sec [75,648..82,347] ~ overlap (-3.9%)
private field decorator 62,188 ops/sec [61,898..62,335] → 58,899 ops/sec [57,954..59,307] 🔴 -5.3% 59,102 ops/sec [58,603..59,477] → 61,973 ops/sec [61,134..62,277] 🟢 +4.9%
plain auto-accessor (no decorator) 105,663 ops/sec [103,177..109,949] → 104,271 ops/sec [103,543..108,367] ~ overlap (-1.3%) 95,793 ops/sec [93,038..103,589] → 109,654 ops/sec [100,349..119,904] ~ overlap (+14.5%)
auto-accessor with decorator 55,490 ops/sec [54,463..57,136] → 55,103 ops/sec [54,256..56,000] ~ overlap (-0.7%) 52,417 ops/sec [51,383..56,835] → 56,493 ops/sec [54,658..61,522] ~ overlap (+7.8%)
decorator writing metadata 45,071 ops/sec [44,862..45,398] → 44,279 ops/sec [44,241..44,503] 🔴 -1.8% 47,941 ops/sec [47,511..52,376] → 50,893 ops/sec [50,165..55,923] ~ overlap (+6.2%)
static getter read 125,548 ops/sec [124,340..126,016] → 128,412 ops/sec [127,537..128,811] 🟢 +2.3% 160,744 ops/sec [159,513..162,343] → 172,072 ops/sec [168,780..173,233] 🟢 +7.0%
static getter/setter pair 101,039 ops/sec [100,855..101,225] → 99,610 ops/sec [99,014..100,379] 🔴 -1.4% 119,574 ops/sec [118,876..121,287] → 128,469 ops/sec [127,516..129,674] 🟢 +7.4%
inherited static getter 76,932 ops/sec [76,767..77,248] → 78,202 ops/sec [77,902..78,321] 🟢 +1.7% 90,291 ops/sec [89,903..91,037] → 97,443 ops/sec [97,282..98,419] 🟢 +7.9%
inherited static setter 82,081 ops/sec [81,682..82,648] → 83,098 ops/sec [82,812..83,132] 🟢 +1.2% 96,194 ops/sec [94,938..98,583] → 106,246 ops/sec [104,737..107,433] 🟢 +10.4%
inherited static getter with this binding 71,382 ops/sec [71,029..71,609] → 71,723 ops/sec [71,317..72,355] ~ overlap (+0.5%) 87,010 ops/sec [86,714..87,635] → 90,013 ops/sec [89,157..90,470] 🟢 +3.5%
closures.js — Interp: 🔴 11 · avg -4.9% · Bytecode: 🟢 6, 5 unch. · avg +3.6%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 141,358 ops/sec [138,709..142,182] → 133,902 ops/sec [132,932..135,205] 🔴 -5.3% 322,545 ops/sec [321,167..323,239] → 332,036 ops/sec [331,800..332,806] 🟢 +2.9%
closure over multiple variables 123,465 ops/sec [122,591..123,876] → 118,967 ops/sec [118,387..119,982] 🔴 -3.6% 315,462 ops/sec [308,390..320,440] → 321,723 ops/sec [318,327..327,209] ~ overlap (+2.0%)
nested closures 129,909 ops/sec [129,311..130,359] → 125,910 ops/sec [125,537..126,301] 🔴 -3.1% 283,856 ops/sec [279,541..295,013] → 292,654 ops/sec [286,470..298,445] ~ overlap (+3.1%)
function as argument 97,220 ops/sec [96,947..97,507] → 91,904 ops/sec [90,827..92,287] 🔴 -5.5% 314,029 ops/sec [311,598..318,880] → 321,841 ops/sec [319,090..326,216] 🟢 +2.5%
function returning function 122,966 ops/sec [122,772..123,069] → 114,886 ops/sec [114,230..115,331] 🔴 -6.6% 344,846 ops/sec [338,600..355,583] → 352,452 ops/sec [346,695..353,497] ~ overlap (+2.2%)
compose two functions 72,300 ops/sec [72,244..72,596] → 69,349 ops/sec [69,110..69,590] 🔴 -4.1% 185,075 ops/sec [180,834..192,468] → 195,108 ops/sec [193,737..198,126] 🟢 +5.4%
fn.call 156,849 ops/sec [155,562..157,420] → 150,257 ops/sec [149,202..150,915] 🔴 -4.2% 212,484 ops/sec [211,557..213,976] → 220,487 ops/sec [208,401..224,043] ~ overlap (+3.8%)
fn.apply 114,860 ops/sec [114,292..115,063] → 112,824 ops/sec [111,330..112,964] 🔴 -1.8% 188,288 ops/sec [187,378..190,349] → 208,644 ops/sec [208,389..208,743] 🟢 +10.8%
fn.bind 150,537 ops/sec [149,856..151,665] → 138,719 ops/sec [137,781..139,377] 🔴 -7.9% 355,837 ops/sec [352,978..357,247] → 376,946 ops/sec [374,575..379,489] 🟢 +5.9%
recursive sum to 50 12,642 ops/sec [12,570..12,890] → 11,880 ops/sec [11,596..11,985] 🔴 -6.0% 48,279 ops/sec [48,040..48,753] → 49,256 ops/sec [48,989..49,636] 🟢 +2.0%
recursive tree traversal 21,906 ops/sec [21,697..21,959] → 20,650 ops/sec [20,485..20,766] 🔴 -5.7% 42,901 ops/sec [42,531..43,357] → 42,664 ops/sec [42,577..42,990] ~ overlap (-0.6%)
collections.js — Interp: 🟢 1, 🔴 9, 2 unch. · avg -5.6% · Bytecode: 🟢 7, 🔴 3, 2 unch. · avg +1.6%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 7,295 ops/sec [7,243..7,321] → 7,002 ops/sec [6,950..7,048] 🔴 -4.0% 7,318 ops/sec [7,298..7,320] → 7,406 ops/sec [7,391..7,433] 🟢 +1.2%
has lookup (50 elements) 92,436 ops/sec [91,848..93,280] → 87,873 ops/sec [87,127..88,192] 🔴 -4.9% 94,121 ops/sec [94,031..94,214] → 97,041 ops/sec [96,805..97,173] 🟢 +3.1%
delete elements 50,805 ops/sec [50,559..50,984] → 46,994 ops/sec [46,801..47,152] 🔴 -7.5% 46,964 ops/sec [46,875..47,009] → 48,609 ops/sec [48,466..48,669] 🟢 +3.5%
forEach iteration 17,043 ops/sec [16,835..17,151] → 15,346 ops/sec [15,113..15,366] 🔴 -10.0% 22,188 ops/sec [21,983..22,301] → 21,357 ops/sec [21,266..21,473] 🔴 -3.7%
spread to array 27,786 ops/sec [27,547..28,016] → 28,038 ops/sec [27,884..28,484] ~ overlap (+0.9%) 219,651 ops/sec [217,722..220,160] → 224,135 ops/sec [223,303..227,150] 🟢 +2.0%
deduplicate array 38,639 ops/sec [37,461..38,980] → 38,378 ops/sec [37,770..38,823] ~ overlap (-0.7%) 67,763 ops/sec [67,539..67,878] → 71,390 ops/sec [70,583..71,461] 🟢 +5.4%
set 50 entries 5,510 ops/sec [5,404..5,694] → 5,067 ops/sec [5,015..5,090] 🔴 -8.0% 5,671 ops/sec [5,607..5,692] → 5,759 ops/sec [5,717..5,810] 🟢 +1.6%
get lookup (50 entries) 92,987 ops/sec [92,219..93,571] → 86,392 ops/sec [85,576..86,928] 🔴 -7.1% 89,569 ops/sec [89,345..89,717] → 88,600 ops/sec [88,518..88,924] 🔴 -1.1%
has check 141,420 ops/sec [140,214..142,934] → 126,734 ops/sec [123,692..128,491] 🔴 -10.4% 137,236 ops/sec [136,992..137,420] → 135,954 ops/sec [135,863..136,168] 🔴 -0.9%
delete entries 50,063 ops/sec [49,369..50,358] → 46,097 ops/sec [45,305..46,328] 🔴 -7.9% 46,266 ops/sec [46,075..46,338] → 46,401 ops/sec [46,214..46,693] ~ overlap (+0.3%)
forEach iteration 17,073 ops/sec [16,999..17,228] → 15,423 ops/sec [15,263..15,673] 🔴 -9.7% 22,165 ops/sec [21,996..22,428] → 22,194 ops/sec [22,095..22,347] ~ overlap (+0.1%)
keys/values/entries 7,590 ops/sec [7,492..7,632] → 7,732 ops/sec [7,676..7,835] 🟢 +1.9% 25,168 ops/sec [25,079..25,328] → 27,199 ops/sec [26,580..27,506] 🟢 +8.1%
destructuring.js — Interp: 🟢 6, 🔴 8, 8 unch. · avg -0.9% · Bytecode: 🟢 15, 7 unch. · avg +5.4%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 391,844 ops/sec [381,341..394,158] → 385,630 ops/sec [380,971..390,919] ~ overlap (-1.6%) 252,533 ops/sec [250,850..254,117] → 272,269 ops/sec [270,806..274,142] 🟢 +7.8%
with rest element 246,596 ops/sec [243,819..248,788] → 247,972 ops/sec [246,937..248,846] ~ overlap (+0.6%) 190,839 ops/sec [190,286..191,157] → 211,770 ops/sec [209,386..212,659] 🟢 +11.0%
with defaults 387,974 ops/sec [384,560..391,264] → 405,502 ops/sec [401,498..406,915] 🟢 +4.5% 294,698 ops/sec [292,794..296,548] → 310,187 ops/sec [309,448..316,396] 🟢 +5.3%
skip elements 410,689 ops/sec [408,646..415,216] → 406,172 ops/sec [404,966..408,240] 🔴 -1.1% 271,421 ops/sec [271,241..271,824] → 296,464 ops/sec [295,685..299,411] 🟢 +9.2%
nested array destructuring 164,514 ops/sec [162,533..165,906] → 173,274 ops/sec [173,011..173,652] 🟢 +5.3% 83,993 ops/sec [83,316..84,114] → 89,363 ops/sec [88,219..90,089] 🟢 +6.4%
swap variables 499,027 ops/sec [497,867..500,260] → 514,172 ops/sec [509,900..517,276] 🟢 +3.0% 317,938 ops/sec [316,063..319,657] → 348,051 ops/sec [345,316..351,745] 🟢 +9.5%
simple object destructuring 315,425 ops/sec [310,957..320,786] → 306,266 ops/sec [301,970..309,640] 🔴 -2.9% 362,872 ops/sec [357,657..368,131] → 377,516 ops/sec [375,333..381,557] 🟢 +4.0%
with defaults 353,893 ops/sec [343,444..365,534] → 346,988 ops/sec [341,621..350,611] ~ overlap (-2.0%) 515,656 ops/sec [511,600..517,482] → 539,082 ops/sec [536,113..547,664] 🟢 +4.5%
with renaming 334,668 ops/sec [332,465..339,281] → 331,884 ops/sec [329,807..335,120] ~ overlap (-0.8%) 373,869 ops/sec [370,993..375,554] → 383,919 ops/sec [381,643..390,717] 🟢 +2.7%
nested object destructuring 148,467 ops/sec [145,917..150,488] → 150,990 ops/sec [150,186..151,812] ~ overlap (+1.7%) 166,072 ops/sec [164,230..167,649] → 178,115 ops/sec [177,283..180,406] 🟢 +7.3%
rest properties 186,556 ops/sec [183,772..187,219] → 184,545 ops/sec [183,707..187,429] ~ overlap (-1.1%) 156,723 ops/sec [155,232..158,746] → 169,228 ops/sec [167,930..170,139] 🟢 +8.0%
object parameter 97,077 ops/sec [96,703..97,563] → 93,884 ops/sec [93,228..95,031] 🔴 -3.3% 137,467 ops/sec [136,050..138,065] → 146,758 ops/sec [145,101..148,084] 🟢 +6.8%
array parameter 123,385 ops/sec [123,306..123,502] → 122,253 ops/sec [121,098..122,993] 🔴 -0.9% 117,147 ops/sec [116,053..118,406] → 126,670 ops/sec [125,946..127,879] 🟢 +8.1%
mixed destructuring in map 35,043 ops/sec [34,884..35,136] → 32,328 ops/sec [32,102..32,493] 🔴 -7.7% 42,516 ops/sec [42,168..42,626] → 43,124 ops/sec [42,624..43,398] ~ overlap (+1.4%)
forEach with array destructuring 62,518 ops/sec [62,216..62,863] → 63,911 ops/sec [63,461..64,110] 🟢 +2.2% 47,760 ops/sec [46,545..48,310] → 47,329 ops/sec [47,075..47,373] ~ overlap (-0.9%)
map with array destructuring 63,297 ops/sec [62,656..63,961] → 63,466 ops/sec [62,695..63,922] ~ overlap (+0.3%) 41,205 ops/sec [40,786..41,383] → 44,750 ops/sec [44,680..44,769] 🟢 +8.6%
filter with array destructuring 64,946 ops/sec [64,676..65,115] → 65,722 ops/sec [65,549..65,872] 🟢 +1.2% 46,318 ops/sec [46,020..47,225] → 47,475 ops/sec [47,152..47,622] ~ overlap (+2.5%)
reduce with array destructuring 70,363 ops/sec [69,904..71,159] → 72,096 ops/sec [71,538..72,470] 🟢 +2.5% 44,210 ops/sec [43,512..44,863] → 50,503 ops/sec [50,004..50,813] 🟢 +14.2%
map with object destructuring 75,610 ops/sec [75,306..75,691] → 71,640 ops/sec [71,361..72,116] 🔴 -5.2% 93,034 ops/sec [92,655..93,261] → 93,179 ops/sec [92,566..94,266] ~ overlap (+0.2%)
map with nested destructuring 63,389 ops/sec [63,083..63,876] → 60,070 ops/sec [59,898..60,514] 🔴 -5.2% 87,946 ops/sec [87,687..88,043] → 88,655 ops/sec [87,358..89,617] ~ overlap (+0.8%)
map with rest in destructuring 41,368 ops/sec [41,260..41,516] → 40,714 ops/sec [40,603..41,336] ~ overlap (-1.6%) 25,259 ops/sec [25,203..25,326] → 25,373 ops/sec [24,820..25,583] ~ overlap (+0.5%)
map with defaults in destructuring 59,123 ops/sec [58,629..59,422] → 55,156 ops/sec [54,546..55,308] 🔴 -6.7% 73,123 ops/sec [73,050..73,760] → 74,285 ops/sec [73,106..75,535] ~ overlap (+1.6%)
fibonacci.js — Interp: 🔴 6, 2 unch. · avg -5.3% · Bytecode: 🟢 5, 🔴 1, 2 unch. · avg +1.7%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 343 ops/sec [342..344] → 316 ops/sec [314..317] 🔴 -7.9% 1,390 ops/sec [1,388..1,391] → 1,406 ops/sec [1,397..1,410] 🟢 +1.1%
recursive fib(20) 31 ops/sec [31..31] → 28 ops/sec [28..28] 🔴 -10.5% 128 ops/sec [127..128] → 126 ops/sec [126..127] 🔴 -0.9%
recursive fib(15) typed 346 ops/sec [345..347] → 315 ops/sec [312..321] 🔴 -8.9% 1,104 ops/sec [1,101..1,108] → 1,124 ops/sec [1,121..1,136] 🟢 +1.8%
recursive fib(20) typed 31 ops/sec [31..31] → 28 ops/sec [28..28] 🔴 -10.1% 103 ops/sec [103..103] → 104 ops/sec [104..104] 🟢 +1.2%
iterative fib(20) via reduce 11,848 ops/sec [11,726..11,906] → 11,853 ops/sec [11,799..11,982] ~ overlap (+0.0%) 20,170 ops/sec [20,113..20,194] → 20,968 ops/sec [20,890..21,163] 🟢 +4.0%
iterator fib(20) 9,643 ops/sec [9,420..9,742] → 9,541 ops/sec [9,397..9,607] ~ overlap (-1.1%) 18,514 ops/sec [18,107..18,912] → 18,582 ops/sec [18,311..18,812] ~ overlap (+0.4%)
iterator fib(20) via Iterator.from + take 15,620 ops/sec [15,562..15,710] → 15,467 ops/sec [15,324..15,547] 🔴 -1.0% 19,660 ops/sec [19,287..19,846] → 20,992 ops/sec [20,912..21,224] 🟢 +6.8%
iterator fib(20) last value via reduce 11,915 ops/sec [11,844..11,995] → 11,578 ops/sec [11,411..11,654] 🔴 -2.8% 14,957 ops/sec [14,796..15,078] → 14,885 ops/sec [14,674..15,015] ~ overlap (-0.5%)
float16array.js — Interp: 🟢 9, 🔴 15, 8 unch. · avg +2.6% · Bytecode: 🟢 28, 4 unch. · avg +3.7%
Benchmark Interpreted Δ Bytecode Δ
new Float16Array(0) 337,243 ops/sec [330,568..342,526] → 330,848 ops/sec [329,985..332,052] ~ overlap (-1.9%) 359,750 ops/sec [355,521..360,641] → 376,426 ops/sec [373,999..379,092] 🟢 +4.6%
new Float16Array(100) 318,648 ops/sec [315,417..321,145] → 311,325 ops/sec [308,607..313,168] 🔴 -2.3% 332,639 ops/sec [330,686..334,024] → 357,116 ops/sec [353,803..360,930] 🟢 +7.4%
new Float16Array(1000) 229,875 ops/sec [228,456..232,307] → 236,580 ops/sec [235,675..238,287] 🟢 +2.9% 243,567 ops/sec [242,116..247,675] → 251,432 ops/sec [248,698..253,271] 🟢 +3.2%
Float16Array.from([...100]) 175,361 ops/sec [172,675..176,778] → 153,555 ops/sec [152,715..154,952] 🔴 -12.4% 157,560 ops/sec [155,937..159,671] → 162,731 ops/sec [161,840..163,844] 🟢 +3.3%
Float16Array.of(1.5, 2.5, 3.5, 4.5, 5.5) 294,120 ops/sec [286,454..299,023] → 294,352 ops/sec [293,974..300,569] ~ overlap (+0.1%) 230,409 ops/sec [228,607..233,877] → 243,124 ops/sec [240,994..244,192] 🟢 +5.5%
new Float16Array(float64Array) 206,427 ops/sec [204,636..208,218] → 201,073 ops/sec [200,701..201,298] 🔴 -2.6% 214,779 ops/sec [212,696..215,823] → 218,086 ops/sec [213,400..219,686] ~ overlap (+1.5%)
sequential write 100 elements 3,351 ops/sec [3,302..3,366] → 3,299 ops/sec [3,262..3,307] ~ overlap (-1.6%) 8,125 ops/sec [8,081..8,171] → 8,314 ops/sec [8,278..8,362] 🟢 +2.3%
sequential read 100 elements 3,550 ops/sec [3,535..3,575] → 3,557 ops/sec [3,546..3,567] ~ overlap (+0.2%) 13,887 ops/sec [13,708..14,179] → 14,176 ops/sec [13,683..14,365] ~ overlap (+2.1%)
write special values (NaN, Inf, -0) 191,471 ops/sec [188,985..199,426] → 196,375 ops/sec [192,810..204,041] ~ overlap (+2.6%) 311,801 ops/sec [311,279..312,703] → 316,291 ops/sec [315,012..319,126] 🟢 +1.4%
Float16Array write 3,360 ops/sec [3,304..3,374] → 3,284 ops/sec [3,274..3,290] 🔴 -2.3% 8,235 ops/sec [8,142..8,317] → 8,521 ops/sec [8,474..8,543] 🟢 +3.5%
Float32Array write 3,380 ops/sec [3,365..3,421] → 3,299 ops/sec [3,264..3,325] 🔴 -2.4% 8,264 ops/sec [8,242..8,280] → 8,454 ops/sec [8,403..8,482] 🟢 +2.3%
Float64Array write 3,378 ops/sec [3,365..3,398] → 3,324 ops/sec [3,314..3,343] 🔴 -1.6% 8,135 ops/sec [8,098..8,245] → 8,580 ops/sec [8,425..8,651] 🟢 +5.5%
Float16Array read 3,381 ops/sec [3,335..3,438] → 3,419 ops/sec [3,394..3,461] ~ overlap (+1.1%) 11,813 ops/sec [11,768..11,850] → 13,065 ops/sec [13,026..13,208] 🟢 +10.6%
Float32Array read 3,525 ops/sec [3,516..3,539] → 3,489 ops/sec [3,449..3,532] ~ overlap (-1.0%) 14,444 ops/sec [14,338..14,595] → 14,730 ops/sec [14,534..15,160] ~ overlap (+2.0%)
Float64Array read 3,575 ops/sec [3,553..3,603] → 3,526 ops/sec [3,509..3,540] 🔴 -1.4% 13,977 ops/sec [13,903..14,133] → 14,486 ops/sec [14,274..14,702] 🟢 +3.6%
fill(1.5) 39,578 ops/sec [39,486..40,751] → 41,383 ops/sec [41,231..41,424] 🟢 +4.6% 40,557 ops/sec [40,509..40,579] → 41,401 ops/sec [41,370..41,440] 🟢 +2.1%
slice() 132,143 ops/sec [129,373..132,696] → 141,443 ops/sec [137,756..143,066] 🟢 +7.0% 141,989 ops/sec [137,970..142,706] → 149,531 ops/sec [147,620..149,784] 🟢 +5.3%
map(x => x * 2) 7,648 ops/sec [7,527..7,729] → 7,073 ops/sec [6,982..7,206] 🔴 -7.5% 9,106 ops/sec [9,083..9,193] → 9,326 ops/sec [9,234..9,384] 🟢 +2.4%
filter(x => x > 25) 7,988 ops/sec [7,924..8,042] → 7,395 ops/sec [7,290..7,458] 🔴 -7.4% 9,413 ops/sec [9,307..9,520] → 9,829 ops/sec [9,758..9,866] 🟢 +4.4%
reduce (sum) 7,584 ops/sec [7,438..7,713] → 7,250 ops/sec [7,123..7,351] 🔴 -4.4% 7,657 ops/sec [7,629..7,707] → 8,371 ops/sec [8,346..8,409] 🟢 +9.3%
sort() 20,837 ops/sec [20,790..20,879] → 27,533 ops/sec [27,453..27,538] 🟢 +32.1% 27,725 ops/sec [27,658..27,761] → 27,813 ops/sec [27,782..27,836] 🟢 +0.3%
indexOf() 137,997 ops/sec [136,773..138,989] → 180,672 ops/sec [180,013..180,822] 🟢 +30.9% 190,304 ops/sec [189,240..191,422] → 192,115 ops/sec [191,881..192,350] 🟢 +1.0%
reverse() 159,139 ops/sec [158,751..159,509] → 193,099 ops/sec [192,030..194,397] 🟢 +21.3% 199,328 ops/sec [198,748..199,456] → 208,229 ops/sec [208,148..208,311] 🟢 +4.5%
toReversed() 62,659 ops/sec [62,356..62,833] → 76,306 ops/sec [75,268..77,373] 🟢 +21.8% 78,955 ops/sec [78,483..79,092] → 79,923 ops/sec [79,780..80,108] 🟢 +1.2%
toSorted() 751 ops/sec [748..754] → 1,011 ops/sec [1,007..1,011] 🟢 +34.7% 1,008 ops/sec [1,003..1,009] → 1,013 ops/sec [1,009..1,014] 🟢 +0.5%
create view over existing buffer 413,271 ops/sec [408,939..417,362] → 388,144 ops/sec [385,114..389,315] 🔴 -6.1% 447,130 ops/sec [442,650..450,324] → 457,100 ops/sec [453,994..458,593] 🟢 +2.2%
subarray() 436,476 ops/sec [435,392..437,818] → 419,538 ops/sec [416,523..426,418] 🔴 -3.9% 547,511 ops/sec [545,267..550,931] → 562,701 ops/sec [559,516..567,619] 🟢 +2.8%
set() from array 598,996 ops/sec [587,010..599,971] → 533,928 ops/sec [533,402..537,176] 🔴 -10.9% 619,152 ops/sec [618,015..620,484] → 628,248 ops/sec [627,140..630,702] 🟢 +1.5%
for-of loop 4,788 ops/sec [4,759..4,894] → 4,803 ops/sec [4,763..4,923] ~ overlap (+0.3%) 19,099 ops/sec [18,860..19,506] → 19,775 ops/sec [19,689..19,839] 🟢 +3.5%
spread into array 15,466 ops/sec [15,219..15,738] → 16,458 ops/sec [16,409..16,598] 🟢 +6.4% 86,413 ops/sec [85,533..87,480] → 98,398 ops/sec [97,631..98,887] 🟢 +13.9%
f16round(1.337) 747,298 ops/sec [742,852..761,617] → 678,751 ops/sec [667,684..686,991] 🔴 -9.2% 653,834 ops/sec [652,947..654,244] → 685,102 ops/sec [676,051..686,722] 🟢 +4.8%
f16round over 100 values 3,586 ops/sec [3,535..3,662] → 3,442 ops/sec [3,388..3,507] 🔴 -4.0% 7,635 ops/sec [7,434..7,702] → 7,659 ops/sec [7,609..7,690] ~ overlap (+0.3%)
for-of.js — Interp: 🔴 2, 5 unch. · avg -1.5% · Bytecode: 🟢 7 · avg +6.5%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 45,673 ops/sec [45,151..45,707] → 44,641 ops/sec [44,269..45,694] ~ overlap (-2.3%) 271,228 ops/sec [267,503..272,684] → 284,679 ops/sec [283,896..286,506] 🟢 +5.0%
for...of with 100-element array 5,229 ops/sec [5,186..5,244] → 5,150 ops/sec [5,078..5,167] 🔴 -1.5% 34,603 ops/sec [34,433..34,738] → 37,051 ops/sec [36,644..37,227] 🟢 +7.1%
for...of with string (10 chars) 33,782 ops/sec [33,257..33,950] → 33,297 ops/sec [32,612..33,413] ~ overlap (-1.4%) 79,232 ops/sec [78,299..80,210] → 81,261 ops/sec [80,475..82,351] 🟢 +2.6%
for...of with Set (10 elements) 45,580 ops/sec [44,834..45,762] → 45,003 ops/sec [44,891..45,279] ~ overlap (-1.3%) 244,071 ops/sec [242,524..245,467] → 263,915 ops/sec [259,744..265,876] 🟢 +8.1%
for...of with Map entries (10 entries) 28,917 ops/sec [28,744..29,179] → 28,807 ops/sec [28,695..29,066] ~ overlap (-0.4%) 30,854 ops/sec [30,225..31,078] → 33,387 ops/sec [33,189..33,522] 🟢 +8.2%
for...of with destructuring 39,680 ops/sec [39,350..40,064] → 38,688 ops/sec [38,317..39,047] 🔴 -2.5% 43,439 ops/sec [42,894..43,857] → 46,968 ops/sec [46,817..47,226] 🟢 +8.1%
for-await-of with sync array 43,689 ops/sec [43,368..43,928] → 43,082 ops/sec [42,124..43,427] ~ overlap (-1.4%) 172,342 ops/sec [170,328..174,602] → 183,664 ops/sec [182,649..184,538] 🟢 +6.6%
helpers/bench-module.js — Interp: 0 · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
iterators.js — Interp: 🟢 1, 🔴 23, 18 unch. · avg -2.2% · Bytecode: 🟢 35, 🔴 2, 5 unch. · avg +6.8%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 15,754 ops/sec [15,348..16,054] → 15,440 ops/sec [15,173..15,625] ~ overlap (-2.0%) 19,387 ops/sec [18,845..19,404] → 20,740 ops/sec [20,642..20,910] 🟢 +7.0%
Iterator.from({next}).toArray() — 50 elements 6,921 ops/sec [6,806..7,016] → 6,794 ops/sec [6,756..6,823] ~ overlap (-1.8%) 8,819 ops/sec [8,633..8,898] → 9,326 ops/sec [9,263..9,360] 🟢 +5.8%
spread pre-wrapped iterator — 20 elements 11,413 ops/sec [11,388..11,445] → 11,398 ops/sec [11,380..11,432] ~ overlap (-0.1%) 18,787 ops/sec [18,609..19,025] → 20,228 ops/sec [19,974..20,307] 🟢 +7.7%
Iterator.from({next}).forEach — 50 elements 4,829 ops/sec [4,718..4,839] → 4,587 ops/sec [4,565..4,610] 🔴 -5.0% 6,524 ops/sec [6,468..6,611] → 6,805 ops/sec [6,747..6,855] 🟢 +4.3%
Iterator.from({next}).reduce — 50 elements 4,904 ops/sec [4,843..4,949] → 4,618 ops/sec [4,500..4,647] 🔴 -5.8% 5,923 ops/sec [5,874..5,983] → 6,314 ops/sec [6,286..6,330] 🟢 +6.6%
wrap array iterator 166,105 ops/sec [162,658..166,554] → 159,494 ops/sec [158,082..161,073] 🔴 -4.0% 180,087 ops/sec [178,147..183,063] → 183,904 ops/sec [180,760..184,115] ~ overlap (+2.1%)
wrap plain {next()} object 11,093 ops/sec [10,866..11,186] → 10,787 ops/sec [10,719..10,865] 🔴 -2.8% 13,849 ops/sec [13,753..14,093] → 14,932 ops/sec [14,709..15,091] 🟢 +7.8%
map + toArray (50 elements) 4,854 ops/sec [4,837..4,863] → 4,615 ops/sec [4,595..4,646] 🔴 -4.9% 6,312 ops/sec [6,299..6,350] → 6,193 ops/sec [6,163..6,221] 🔴 -1.9%
filter + toArray (50 elements) 4,731 ops/sec [4,716..4,741] → 4,508 ops/sec [4,482..4,549] 🔴 -4.7% 6,024 ops/sec [6,008..6,079] → 6,354 ops/sec [6,347..6,372] 🟢 +5.5%
take(10) + toArray (50 element source) 28,684 ops/sec [27,944..29,209] → 28,238 ops/sec [27,741..28,307] ~ overlap (-1.6%) 35,250 ops/sec [35,040..35,858] → 35,412 ops/sec [35,339..35,636] ~ overlap (+0.5%)
drop(40) + toArray (50 element source) 6,934 ops/sec [6,785..6,966] → 6,789 ops/sec [6,746..6,818] ~ overlap (-2.1%) 8,949 ops/sec [8,896..9,005] → 9,503 ops/sec [9,292..9,614] 🟢 +6.2%
chained map + filter + take (100 element source) 8,711 ops/sec [8,639..8,734] → 8,084 ops/sec [8,042..8,192] 🔴 -7.2% 10,863 ops/sec [10,753..10,994] → 11,125 ops/sec [11,107..11,269] 🟢 +2.4%
some + every (50 elements) 2,793 ops/sec [2,775..2,804] → 2,638 ops/sec [2,615..2,667] 🔴 -5.5% 3,697 ops/sec [3,672..3,707] → 3,839 ops/sec [3,752..3,881] 🟢 +3.8%
find (50 elements) 6,072 ops/sec [6,028..6,081] → 5,762 ops/sec [5,688..5,841] 🔴 -5.1% 7,658 ops/sec [7,626..7,688] → 8,392 ops/sec [8,345..8,437] 🟢 +9.6%
concat 2 arrays (10 + 10 elements) 162,238 ops/sec [158,812..166,048] → 158,199 ops/sec [157,526..158,643] 🔴 -2.5% 164,970 ops/sec [164,450..165,996] → 172,066 ops/sec [170,751..172,737] 🟢 +4.3%
concat 5 arrays (10 elements each) 98,852 ops/sec [98,194..99,637] → 97,526 ops/sec [96,752..98,137] 🔴 -1.3% 103,802 ops/sec [103,378..104,426] → 109,418 ops/sec [108,841..109,718] 🟢 +5.4%
concat 2 arrays (20 + 20 elements) 143,962 ops/sec [143,413..145,274] → 142,752 ops/sec [142,340..143,487] ~ overlap (-0.8%) 153,834 ops/sec [152,631..154,352] → 149,779 ops/sec [149,506..149,978] 🔴 -2.6%
concat + filter + toArray (20 + 20 elements) 16,719 ops/sec [16,608..16,945] → 15,680 ops/sec [15,522..15,827] 🔴 -6.2% 22,590 ops/sec [22,514..22,671] → 23,825 ops/sec [23,679..24,030] 🟢 +5.5%
concat + map + take (20 + 20 elements, take 10) 48,898 ops/sec [48,779..49,116] → 45,784 ops/sec [45,683..46,078] 🔴 -6.4% 58,612 ops/sec [58,211..58,821] → 62,593 ops/sec [62,265..62,894] 🟢 +6.8%
concat Sets (15 + 15 elements) 147,180 ops/sec [145,938..148,851] → 143,656 ops/sec [143,169..144,503] 🔴 -2.4% 152,305 ops/sec [145,777..152,992] → 151,033 ops/sec [149,048..153,196] ~ overlap (-0.8%)
concat strings (13 + 13 characters) 106,279 ops/sec [105,452..106,541] → 105,210 ops/sec [104,535..105,282] 🔴 -1.0% 98,023 ops/sec [96,864..99,299] → 111,901 ops/sec [110,363..112,254] 🟢 +14.2%
zip 2 arrays (10 + 10 elements) 52,454 ops/sec [52,028..53,168] → 54,763 ops/sec [53,007..54,989] ~ overlap (+4.4%) 52,686 ops/sec [52,232..53,761] → 59,096 ops/sec [58,665..59,406] 🟢 +12.2%
zip 3 arrays (10 elements each) 49,856 ops/sec [48,018..50,864] → 50,121 ops/sec [49,848..50,404] ~ overlap (+0.5%) 48,811 ops/sec [48,586..49,351] → 55,120 ops/sec [53,726..55,424] 🟢 +12.9%
zip 2 arrays (20 + 20 elements) 35,477 ops/sec [35,217..35,572] → 36,157 ops/sec [35,570..36,399] ~ overlap (+1.9%) 34,379 ops/sec [33,864..34,486] → 38,898 ops/sec [38,754..39,120] 🟢 +13.1%
zip 2 arrays (50 + 50 elements) 18,191 ops/sec [18,152..18,424] → 18,268 ops/sec [18,182..18,529] ~ overlap (+0.4%) 16,613 ops/sec [16,389..16,741] → 19,646 ops/sec [19,291..19,885] 🟢 +18.3%
zip shortest mode (20 + 10 elements) 52,914 ops/sec [51,779..53,813] → 54,927 ops/sec [54,307..55,433] 🟢 +3.8% 53,530 ops/sec [53,343..53,911] → 58,703 ops/sec [58,205..58,960] 🟢 +9.7%
zip longest mode (10 + 20 elements) 31,182 ops/sec [31,027..32,081] → 31,965 ops/sec [31,834..32,147] ~ overlap (+2.5%) 29,754 ops/sec [29,492..30,273] → 34,312 ops/sec [33,823..34,719] 🟢 +15.3%
zip strict mode (20 + 20 elements) 33,459 ops/sec [33,166..34,214] → 34,554 ops/sec [33,717..34,682] ~ overlap (+3.3%) 33,552 ops/sec [33,355..33,707] → 37,020 ops/sec [36,707..37,371] 🟢 +10.3%
zip + map + toArray (20 + 20 elements) 17,580 ops/sec [17,337..17,808] → 17,077 ops/sec [16,745..17,190] 🔴 -2.9% 11,465 ops/sec [11,323..11,524] → 12,239 ops/sec [12,094..12,361] 🟢 +6.8%
zip + filter + toArray (20 + 20 elements) 16,539 ops/sec [16,381..16,583] → 16,116 ops/sec [15,932..16,309] 🔴 -2.6% 11,639 ops/sec [11,515..11,729] → 12,295 ops/sec [12,201..12,346] 🟢 +5.6%
zip Sets (15 + 15 elements) 41,785 ops/sec [41,284..42,298] → 43,331 ops/sec [42,039..43,706] ~ overlap (+3.7%) 40,431 ops/sec [39,652..40,923] → 46,773 ops/sec [46,439..47,026] 🟢 +15.7%
zipKeyed 2 keys (10 elements each) 51,285 ops/sec [50,724..52,535] → 52,105 ops/sec [50,917..52,525] ~ overlap (+1.6%) 52,963 ops/sec [51,279..53,600] → 58,553 ops/sec [58,383..58,746] 🟢 +10.6%
zipKeyed 3 keys (20 elements each) 25,851 ops/sec [25,444..26,360] → 26,118 ops/sec [25,823..26,809] ~ overlap (+1.0%) 28,053 ops/sec [27,646..28,437] → 29,612 ops/sec [29,433..30,483] 🟢 +5.6%
zipKeyed longest mode (10 + 20 elements) 29,388 ops/sec [29,090..29,508] → 29,556 ops/sec [29,100..29,770] ~ overlap (+0.6%) 29,345 ops/sec [29,154..29,553] → 33,011 ops/sec [32,872..33,243] 🟢 +12.5%
zipKeyed strict mode (20 + 20 elements) 30,797 ops/sec [30,471..31,100] → 31,525 ops/sec [30,838..32,029] ~ overlap (+2.4%) 31,733 ops/sec [31,446..31,815] → 33,271 ops/sec [32,975..33,418] 🟢 +4.8%
zipKeyed + filter + map (20 elements) 12,771 ops/sec [12,749..12,828] → 12,086 ops/sec [12,009..12,123] 🔴 -5.4% 14,371 ops/sec [14,334..14,483] → 15,729 ops/sec [15,549..15,871] 🟢 +9.4%
array.values().map().filter().toArray() 8,468 ops/sec [8,380..8,580] → 7,689 ops/sec [7,511..7,740] 🔴 -9.2% 11,084 ops/sec [11,060..11,095] → 11,577 ops/sec [11,436..11,727] 🟢 +4.5%
array.values().take(5).toArray() 217,333 ops/sec [216,349..219,649] → 206,388 ops/sec [204,886..208,332] 🔴 -5.0% 235,742 ops/sec [232,772..237,059] → 244,224 ops/sec [241,202..245,441] 🟢 +3.6%
array.values().drop(45).toArray() 202,103 ops/sec [198,805..203,356] → 194,525 ops/sec [191,376..196,279] 🔴 -3.7% 219,159 ops/sec [217,431..220,340] → 219,261 ops/sec [218,086..221,878] ~ overlap (+0.0%)
map.entries() chained helpers 10,203 ops/sec [10,149..10,210] → 9,760 ops/sec [9,573..9,830] 🔴 -4.3% 6,052 ops/sec [5,983..6,054] → 6,537 ops/sec [6,494..6,600] 🟢 +8.0%
set.values() chained helpers 17,838 ops/sec [17,532..17,991] → 16,096 ops/sec [15,855..16,415] 🔴 -9.8% 23,646 ops/sec [23,618..23,810] → 24,280 ops/sec [23,581..24,587] ~ overlap (+2.7%)
string iterator map + toArray 13,351 ops/sec [13,278..13,450] → 13,245 ops/sec [13,045..13,387] ~ overlap (-0.8%) 14,775 ops/sec [14,588..14,957] → 15,482 ops/sec [15,429..15,546] 🟢 +4.8%
json.js — Interp: 🔴 19, 1 unch. · avg -6.8% · Bytecode: 🟢 17, 🔴 1, 2 unch. · avg +4.0%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 155,381 ops/sec [152,158..156,535] → 144,104 ops/sec [140,245..147,498] 🔴 -7.3% 142,080 ops/sec [128,531..142,850] → 148,962 ops/sec [147,174..150,803] 🟢 +4.8%
parse nested object 96,948 ops/sec [95,791..98,248] → 91,112 ops/sec [90,624..91,709] 🔴 -6.0% 91,009 ops/sec [89,216..91,253] → 94,657 ops/sec [93,907..95,823] 🟢 +4.0%
parse array of objects 56,189 ops/sec [55,978..56,440] → 53,592 ops/sec [53,238..54,038] 🔴 -4.6% 52,919 ops/sec [52,270..53,412] → 55,600 ops/sec [54,612..56,241] 🟢 +5.1%
parse large flat object 62,950 ops/sec [62,248..63,185] → 60,378 ops/sec [59,513..60,832] 🔴 -4.1% 59,733 ops/sec [59,469..60,167] → 62,140 ops/sec [61,674..62,576] 🟢 +4.0%
parse mixed types 72,922 ops/sec [72,185..72,957] → 65,931 ops/sec [65,338..66,633] 🔴 -9.6% 66,113 ops/sec [65,402..66,907] → 68,833 ops/sec [68,549..69,533] 🟢 +4.1%
stringify simple object 164,641 ops/sec [162,188..164,791] → 150,619 ops/sec [149,099..151,505] 🔴 -8.5% 149,010 ops/sec [148,504..150,024] → 153,606 ops/sec [151,794..154,859] 🟢 +3.1%
stringify nested object 90,613 ops/sec [90,563..90,955] → 84,603 ops/sec [82,978..85,489] 🔴 -6.6% 81,169 ops/sec [80,657..81,451] → 83,552 ops/sec [83,401..83,949] 🟢 +2.9%
stringify array of objects 41,203 ops/sec [41,035..41,319] → 36,495 ops/sec [36,291..36,781] 🔴 -11.4% 37,363 ops/sec [37,314..37,517] → 36,761 ops/sec [36,721..36,840] 🔴 -1.6%
stringify mixed types 68,032 ops/sec [67,876..68,098] → 63,347 ops/sec [62,085..64,111] 🔴 -6.9% 60,403 ops/sec [60,161..60,822] → 62,706 ops/sec [62,468..63,408] 🟢 +3.8%
reviver doubles numbers 33,334 ops/sec [33,018..33,443] → 31,779 ops/sec [31,411..32,027] 🔴 -4.7% 34,247 ops/sec [34,035..34,484] → 36,445 ops/sec [35,571..36,906] 🟢 +6.4%
reviver filters properties 31,641 ops/sec [31,398..31,919] → 29,452 ops/sec [29,393..29,604] 🔴 -6.9% 29,825 ops/sec [29,721..30,195] → 31,513 ops/sec [30,885..31,900] 🟢 +5.7%
reviver on nested object 38,173 ops/sec [37,879..38,633] → 36,405 ops/sec [36,189..36,678] 🔴 -4.6% 37,181 ops/sec [36,982..37,478] → 39,229 ops/sec [38,902..39,819] 🟢 +5.5%
reviver on array 21,037 ops/sec [20,214..21,194] → 20,044 ops/sec [19,857..20,307] ~ overlap (-4.7%) 21,359 ops/sec [21,239..21,614] → 22,409 ops/sec [22,078..22,805] 🟢 +4.9%
replacer function doubles numbers 42,469 ops/sec [41,030..42,702] → 39,617 ops/sec [38,528..39,896] 🔴 -6.7% 42,039 ops/sec [41,676..42,789] → 44,772 ops/sec [44,451..45,224] 🟢 +6.5%
replacer function excludes properties 56,492 ops/sec [55,865..56,674] → 53,347 ops/sec [53,224..53,410] 🔴 -5.6% 52,304 ops/sec [52,187..52,454] → 54,182 ops/sec [53,752..54,458] 🟢 +3.6%
array replacer (allowlist) 97,446 ops/sec [96,067..98,733] → 91,532 ops/sec [90,128..93,819] 🔴 -6.1% 88,218 ops/sec [87,782..89,256] → 93,200 ops/sec [92,445..94,212] 🟢 +5.6%
stringify with 2-space indent 78,889 ops/sec [75,864..79,368] → 73,668 ops/sec [72,465..73,953] 🔴 -6.6% 72,106 ops/sec [71,779..74,028] → 73,942 ops/sec [73,311..74,929] ~ overlap (+2.5%)
stringify with tab indent 79,524 ops/sec [79,176..79,743] → 74,107 ops/sec [72,799..75,072] 🔴 -6.8% 71,554 ops/sec [71,428..71,825] → 73,894 ops/sec [73,381..74,833] 🟢 +3.3%
parse then stringify 47,179 ops/sec [46,325..47,594] → 42,849 ops/sec [42,435..42,880] 🔴 -9.2% 43,723 ops/sec [43,437..43,986] → 45,603 ops/sec [45,239..46,008] 🟢 +4.3%
stringify then parse 27,813 ops/sec [27,599..27,972] → 25,186 ops/sec [25,022..25,602] 🔴 -9.4% 25,971 ops/sec [25,943..26,111] → 26,565 ops/sec [26,016..26,819] ~ overlap (+2.3%)
jsx.jsx — Interp: 🔴 13, 8 unch. · avg -1.7% · Bytecode: 🟢 17, 🔴 1, 3 unch. · avg +4.2%
Benchmark Interpreted Δ Bytecode Δ
simple element 220,527 ops/sec [218,652..221,960] → 211,761 ops/sec [210,440..213,571] 🔴 -4.0% 299,286 ops/sec [297,389..302,250] → 315,378 ops/sec [313,889..316,923] 🟢 +5.4%
self-closing element 227,854 ops/sec [226,438..230,293] → 222,353 ops/sec [220,069..224,024] 🔴 -2.4% 349,451 ops/sec [345,543..354,359] → 354,988 ops/sec [354,504..356,711] 🟢 +1.6%
element with string attribute 184,649 ops/sec [182,710..185,048] → 179,686 ops/sec [179,464..180,282] 🔴 -2.7% 234,589 ops/sec [233,647..235,481] → 246,815 ops/sec [245,103..247,711] 🟢 +5.2%
element with multiple attributes 160,897 ops/sec [160,546..161,362] → 155,860 ops/sec [154,819..156,415] 🔴 -3.1% 187,665 ops/sec [186,939..190,903] → 196,556 ops/sec [195,667..198,489] 🟢 +4.7%
element with expression attribute 173,420 ops/sec [173,217..174,388] → 167,880 ops/sec [166,570..169,417] 🔴 -3.2% 235,235 ops/sec [234,624..237,187] → 257,599 ops/sec [256,925..258,708] 🟢 +9.5%
text child 216,411 ops/sec [214,426..217,402] → 213,384 ops/sec [212,309..214,306] 🔴 -1.4% 316,575 ops/sec [316,188..320,226] → 311,239 ops/sec [308,540..313,433] 🔴 -1.7%
expression child 211,364 ops/sec [210,010..212,592] → 209,445 ops/sec [207,386..210,327] ~ overlap (-0.9%) 301,609 ops/sec [297,022..305,021] → 315,984 ops/sec [315,380..316,413] 🟢 +4.8%
mixed text and expression 196,813 ops/sec [194,633..198,712] → 196,273 ops/sec [195,987..197,417] ~ overlap (-0.3%) 268,338 ops/sec [267,154..270,107] → 276,403 ops/sec [275,218..277,608] 🟢 +3.0%
nested elements (3 levels) 82,446 ops/sec [82,188..82,577] → 79,527 ops/sec [79,275..79,725] 🔴 -3.5% 114,990 ops/sec [114,179..115,436] → 117,734 ops/sec [116,530..118,945] 🟢 +2.4%
sibling children 61,624 ops/sec [61,243..61,995] → 59,240 ops/sec [58,976..59,300] 🔴 -3.9% 86,813 ops/sec [85,603..87,504] → 87,542 ops/sec [86,870..88,293] ~ overlap (+0.8%)
component element 152,981 ops/sec [152,466..154,534] → 150,865 ops/sec [150,631..151,104] 🔴 -1.4% 212,275 ops/sec [210,531..213,860] → 232,497 ops/sec [231,262..235,861] 🟢 +9.5%
component with children 95,955 ops/sec [94,934..96,357] → 92,765 ops/sec [92,284..92,963] 🔴 -3.3% 136,031 ops/sec [135,717..136,657] → 136,366 ops/sec [134,522..136,814] ~ overlap (+0.2%)
dotted component 126,911 ops/sec [126,378..127,848] → 126,497 ops/sec [126,165..127,063] ~ overlap (-0.3%) 161,069 ops/sec [158,599..163,871] → 171,210 ops/sec [170,311..172,227] 🟢 +6.3%
empty fragment 225,356 ops/sec [224,377..226,100] → 220,391 ops/sec [219,338..221,385] 🔴 -2.2% 385,773 ops/sec [384,774..387,833] → 397,482 ops/sec [394,868..399,316] 🟢 +3.0%
fragment with children 60,787 ops/sec [60,503..61,389] → 59,063 ops/sec [58,899..59,294] 🔴 -2.8% 83,501 ops/sec [83,149..84,074] → 86,752 ops/sec [86,459..87,079] 🟢 +3.9%
spread attributes 110,278 ops/sec [109,831..110,885] → 109,999 ops/sec [106,065..110,579] ~ overlap (-0.3%) 123,142 ops/sec [122,677..123,448] → 130,075 ops/sec [128,176..131,484] 🟢 +5.6%
spread with overrides 96,886 ops/sec [96,599..97,370] → 95,908 ops/sec [94,630..96,478] 🔴 -1.0% 105,776 ops/sec [105,015..106,197] → 113,847 ops/sec [112,965..114,020] 🟢 +7.6%
shorthand props 160,899 ops/sec [157,249..162,770] → 162,683 ops/sec [162,239..163,013] ~ overlap (+1.1%) 226,291 ops/sec [224,726..228,059] → 226,331 ops/sec [224,906..228,747] ~ overlap (+0.0%)
nav bar structure 27,802 ops/sec [27,574..27,948] → 27,725 ops/sec [27,455..27,856] ~ overlap (-0.3%) 37,043 ops/sec [36,936..37,417] → 39,616 ops/sec [39,404..39,995] 🟢 +6.9%
card component tree 32,281 ops/sec [32,193..32,485] → 32,428 ops/sec [32,295..32,595] ~ overlap (+0.5%) 41,596 ops/sec [41,315..42,133] → 43,686 ops/sec [43,490..44,318] 🟢 +5.0%
10 list items via Array.from 14,814 ops/sec [14,669..14,921] → 14,659 ops/sec [14,181..14,754] ~ overlap (-1.1%) 18,262 ops/sec [18,169..18,383] → 19,186 ops/sec [19,096..19,278] 🟢 +5.1%
modules.js — Interp: 🔴 9 · avg -5.0% · Bytecode: 🟢 9 · avg +3.4%
Benchmark Interpreted Δ Bytecode Δ
call imported function 483,263 ops/sec [477,996..485,659] → 451,877 ops/sec [447,372..460,680] 🔴 -6.5% 649,071 ops/sec [643,039..655,693] → 694,971 ops/sec [690,878..701,111] 🟢 +7.1%
call two imported functions 271,257 ops/sec [270,056..274,056] → 250,325 ops/sec [249,206..252,739] 🔴 -7.7% 350,813 ops/sec [348,883..354,681] → 372,257 ops/sec [371,724..380,506] 🟢 +6.1%
read imported constant 1,638,831 ops/sec [1,624,062..1,647,687] → 1,612,997 ops/sec [1,609,123..1,621,781] 🔴 -1.6% 5,571,466 ops/sec [5,560,900..5,584,589] → 5,758,907 ops/sec [5,749,724..5,770,380] 🟢 +3.4%
read imported string 1,640,795 ops/sec [1,629,390..1,652,676] → 1,573,019 ops/sec [1,560,903..1,609,840] 🔴 -4.1% 5,545,281 ops/sec [5,525,314..5,560,680] → 5,712,619 ops/sec [5,700,604..5,713,761] 🟢 +3.0%
read JSON string property 1,652,395 ops/sec [1,637,373..1,667,540] → 1,602,146 ops/sec [1,594,113..1,613,853] 🔴 -3.0% 5,540,612 ops/sec [5,524,961..5,546,166] → 5,710,326 ops/sec [5,698,136..5,718,608] 🟢 +3.1%
read JSON number property 1,604,886 ops/sec [1,592,957..1,614,943] → 1,556,315 ops/sec [1,544,336..1,565,666] 🔴 -3.0% 5,547,892 ops/sec [5,513,000..5,554,546] → 5,714,721 ops/sec [5,703,871..5,721,550] 🟢 +3.0%
read JSON boolean property 1,669,780 ops/sec [1,641,004..1,674,062] → 1,576,097 ops/sec [1,567,909..1,587,440] 🔴 -5.6% 5,561,885 ops/sec [5,548,365..5,568,028] → 5,715,589 ops/sec [5,713,354..5,719,869] 🟢 +2.8%
read JSON array property 1,648,093 ops/sec [1,640,395..1,664,803] → 1,574,225 ops/sec [1,570,764..1,586,522] 🔴 -4.5% 5,555,052 ops/sec [5,542,149..5,566,626] → 5,641,726 ops/sec [5,625,464..5,759,360] 🟢 +1.6%
read multiple JSON properties 972,668 ops/sec [966,688..980,204] → 889,147 ops/sec [885,259..894,852] 🔴 -8.6% 4,814,916 ops/sec [4,812,486..4,815,662] → 4,857,536 ops/sec [4,850,599..4,862,585] 🟢 +0.9%
numbers.js — Interp: 🟢 1, 🔴 9, 1 unch. · avg -6.1% · Bytecode: 🟢 6, 🔴 1, 4 unch. · avg +2.9%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 498,648 ops/sec [486,722..512,997] → 496,366 ops/sec [488,145..498,181] ~ overlap (-0.5%) 1,755,497 ops/sec [1,674,612..1,781,476] → 1,763,567 ops/sec [1,697,905..1,785,744] ~ overlap (+0.5%)
floating point arithmetic 572,900 ops/sec [569,428..575,984] → 596,568 ops/sec [594,101..603,258] 🟢 +4.1% 918,895 ops/sec [913,683..920,782] → 926,750 ops/sec [913,507..934,988] ~ overlap (+0.9%)
number coercion 201,692 ops/sec [200,618..202,810] → 177,346 ops/sec [172,141..178,244] 🔴 -12.1% 201,538 ops/sec [201,204..203,197] → 215,835 ops/sec [213,970..216,376] 🟢 +7.1%
toFixed 104,171 ops/sec [103,735..104,901] → 100,305 ops/sec [99,231..101,123] 🔴 -3.7% 110,671 ops/sec [109,834..112,292] → 113,070 ops/sec [111,733..114,944] ~ overlap (+2.2%)
toString 156,219 ops/sec [155,350..157,472] → 148,644 ops/sec [147,414..149,867] 🔴 -4.8% 179,789 ops/sec [178,032..182,037] → 190,492 ops/sec [189,913..190,713] 🟢 +6.0%
valueOf 234,444 ops/sec [232,789..236,847] → 225,045 ops/sec [223,803..226,355] 🔴 -4.0% 258,938 ops/sec [256,633..259,430] → 254,458 ops/sec [253,270..255,045] 🔴 -1.7%
toPrecision 144,295 ops/sec [142,988..145,153] → 138,874 ops/sec [138,100..140,536] 🔴 -3.8% 156,634 ops/sec [155,848..158,483] → 165,840 ops/sec [164,901..167,719] 🟢 +5.9%
Number.isNaN 343,014 ops/sec [342,006..346,244] → 308,454 ops/sec [302,977..310,670] 🔴 -10.1% 301,789 ops/sec [299,551..302,598] → 309,618 ops/sec [307,041..311,533] 🟢 +2.6%
Number.isFinite 345,958 ops/sec [343,388..350,329] → 303,728 ops/sec [301,017..305,044] 🔴 -12.2% 286,981 ops/sec [286,245..287,600] → 294,944 ops/sec [291,483..296,319] 🟢 +2.8%
Number.isInteger 351,000 ops/sec [348,352..353,070] → 312,174 ops/sec [309,759..316,112] 🔴 -11.1% 312,096 ops/sec [309,256..312,609] → 313,976 ops/sec [311,628..316,014] ~ overlap (+0.6%)
Number.parseInt and parseFloat 268,472 ops/sec [265,745..271,388] → 245,411 ops/sec [242,242..249,895] 🔴 -8.6% 229,167 ops/sec [228,243..230,395] → 242,168 ops/sec [239,820..245,185] 🟢 +5.7%
objects.js — Interp: 🟢 2, 🔴 4, 1 unch. · avg -0.4% · Bytecode: 🟢 6, 🔴 1 · avg +5.1%
Benchmark Interpreted Δ Bytecode Δ
create simple object 463,236 ops/sec [458,401..467,745] → 476,962 ops/sec [473,600..479,867] 🟢 +3.0% 553,403 ops/sec [550,459..556,582] → 588,892 ops/sec [584,964..589,465] 🟢 +6.4%
create nested object 224,110 ops/sec [222,285..225,952] → 236,316 ops/sec [235,647..237,011] 🟢 +5.4% 226,518 ops/sec [223,659..228,378] → 244,417 ops/sec [242,833..246,495] 🟢 +7.9%
create 50 objects via Array.from 9,380 ops/sec [9,355..9,464] → 9,329 ops/sec [9,302..9,342] 🔴 -0.5% 9,638 ops/sec [9,565..9,718] → 9,792 ops/sec [9,756..9,838] 🟢 +1.6%
property read 565,284 ops/sec [544,747..567,805] → 554,467 ops/sec [546,224..569,001] ~ overlap (-1.9%) 773,680 ops/sec [772,597..774,590] → 750,056 ops/sec [746,396..752,332] 🔴 -3.1%
Object.keys 270,726 ops/sec [268,384..273,749] → 265,814 ops/sec [265,297..266,408] 🔴 -1.8% 286,569 ops/sec [285,849..289,074] → 308,719 ops/sec [305,658..313,132] 🟢 +7.7%
Object.entries 100,732 ops/sec [100,108..101,243] → 96,366 ops/sec [95,273..96,604] 🔴 -4.3% 109,366 ops/sec [108,312..110,407] → 112,475 ops/sec [111,718..113,668] 🟢 +2.8%
spread operator 182,291 ops/sec [181,890..184,602] → 177,517 ops/sec [176,892..178,664] 🔴 -2.6% 187,846 ops/sec [185,442..192,558] → 210,934 ops/sec [209,627..212,915] 🟢 +12.3%
promises.js — Interp: 🔴 9, 3 unch. · avg -3.9% · Bytecode: 🟢 12 · avg +6.5%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 543,015 ops/sec [541,680..544,517] → 508,275 ops/sec [504,871..511,044] 🔴 -6.4% 537,392 ops/sec [533,606..543,901] → 562,918 ops/sec [562,568..564,013] 🟢 +4.7%
new Promise(resolve => resolve(value)) 189,356 ops/sec [188,220..191,409] → 186,742 ops/sec [185,623..187,107] 🔴 -1.4% 224,477 ops/sec [223,606..225,433] → 235,381 ops/sec [235,090..236,734] 🟢 +4.9%
Promise.reject(reason) 555,109 ops/sec [547,276..564,271] → 517,510 ops/sec [507,069..520,145] 🔴 -6.8% 513,974 ops/sec [507,200..518,799] → 530,793 ops/sec [527,947..531,283] 🟢 +3.3%
resolve + then (1 handler) 175,125 ops/sec [155,104..177,002] → 169,926 ops/sec [168,782..171,161] ~ overlap (-3.0%) 205,151 ops/sec [204,780..207,082] → 216,641 ops/sec [214,989..217,501] 🟢 +5.6%
resolve + then chain (3 deep) 72,182 ops/sec [70,241..73,031] → 68,920 ops/sec [68,080..69,693] 🔴 -4.5% 79,903 ops/sec [79,087..80,458] → 88,546 ops/sec [87,726..89,226] 🟢 +10.8%
resolve + then chain (10 deep) 22,734 ops/sec [22,123..23,202] → 22,023 ops/sec [21,871..22,313] ~ overlap (-3.1%) 26,369 ops/sec [26,019..26,483] → 28,816 ops/sec [28,642..29,027] 🟢 +9.3%
reject + catch + then 100,726 ops/sec [99,734..103,428] → 97,945 ops/sec [97,436..98,852] 🔴 -2.8% 105,124 ops/sec [104,823..106,163] → 116,624 ops/sec [116,392..117,203] 🟢 +10.9%
resolve + finally + then 84,015 ops/sec [83,142..84,653] → 82,000 ops/sec [81,613..82,150] 🔴 -2.4% 91,087 ops/sec [90,732..91,570] → 98,709 ops/sec [98,332..98,888] 🟢 +8.4%
Promise.all (5 resolved) 31,332 ops/sec [29,289..31,542] → 30,441 ops/sec [30,313..30,530] ~ overlap (-2.8%) 32,167 ops/sec [31,998..32,650] → 33,877 ops/sec [33,515..33,962] 🟢 +5.3%
Promise.race (5 resolved) 33,751 ops/sec [33,172..34,095] → 32,450 ops/sec [32,127..32,610] 🔴 -3.9% 34,110 ops/sec [33,788..34,228] → 35,877 ops/sec [35,658..36,153] 🟢 +5.2%
Promise.allSettled (5 mixed) 26,717 ops/sec [26,204..26,913] → 25,351 ops/sec [25,264..25,479] 🔴 -5.1% 26,485 ops/sec [26,201..26,698] → 28,337 ops/sec [27,915..28,568] 🟢 +7.0%
Promise.any (5 mixed) 31,581 ops/sec [31,522..31,829] → 30,270 ops/sec [29,788..30,473] 🔴 -4.2% 32,260 ops/sec [32,159..32,534] → 33,160 ops/sec [32,972..33,370] 🟢 +2.8%
regexp.js — Interp: 🔴 10, 1 unch. · avg -6.5% · Bytecode: 🟢 6, 🔴 1, 4 unch. · avg +1.7%
Benchmark Interpreted Δ Bytecode Δ
regex literal creation 151,548 ops/sec [149,940..152,684] → 149,421 ops/sec [147,535..149,954] ~ overlap (-1.4%) 133,485 ops/sec [133,173..134,133] → 138,213 ops/sec [137,178..138,893] 🟢 +3.5%
new RegExp(pattern, flags) 133,284 ops/sec [132,717..136,121] → 128,393 ops/sec [127,524..128,932] 🔴 -3.7% 132,821 ops/sec [131,495..134,554] → 138,894 ops/sec [136,465..139,554] 🟢 +4.6%
RegExp(existingRegex) returns the same regex 787,107 ops/sec [783,089..791,661] → 678,082 ops/sec [675,536..679,301] 🔴 -13.9% 1,009,885 ops/sec [1,006,866..1,012,916] → 1,056,159 ops/sec [1,051,233..1,058,302] 🟢 +4.6%
test() on a global regex 145,546 ops/sec [144,757..146,243] → 133,208 ops/sec [131,847..135,319] 🔴 -8.5% 158,152 ops/sec [157,415..158,949] → 155,957 ops/sec [155,478..157,807] ~ overlap (-1.4%)
exec() with capture groups 122,899 ops/sec [121,330..124,279] → 115,499 ops/sec [113,820..116,868] 🔴 -6.0% 128,351 ops/sec [126,843..130,495] → 129,547 ops/sec [129,011..129,742] ~ overlap (+0.9%)
toString() 490,817 ops/sec [484,575..494,412] → 466,831 ops/sec [457,890..467,858] 🔴 -4.9% 627,837 ops/sec [626,383..629,221] → 653,195 ops/sec [652,141..655,200] 🟢 +4.0%
match() with global regex 39,841 ops/sec [39,652..40,441] → 37,051 ops/sec [36,757..37,531] 🔴 -7.0% 37,585 ops/sec [37,182..37,900] → 37,970 ops/sec [37,858..38,247] ~ overlap (+1.0%)
matchAll() with capture groups 21,175 ops/sec [20,655..22,074] → 19,789 ops/sec [19,689..19,868] 🔴 -6.5% 23,977 ops/sec [23,802..24,065] → 24,488 ops/sec [24,359..24,992] 🟢 +2.1%
replace() with global regex 39,555 ops/sec [38,934..39,788] → 36,150 ops/sec [35,685..36,606] 🔴 -8.6% 36,547 ops/sec [36,461..36,786] → 35,860 ops/sec [35,824..35,909] 🔴 -1.9%
search() with regex 80,250 ops/sec [78,574..80,536] → 75,605 ops/sec [74,922..76,652] 🔴 -5.8% 73,226 ops/sec [73,035..73,534] → 74,257 ops/sec [73,902..74,338] 🟢 +1.4%
split() with regex separator 39,217 ops/sec [38,962..39,369] → 37,373 ops/sec [37,046..37,622] 🔴 -4.7% 37,784 ops/sec [37,638..37,857] → 37,888 ops/sec [37,351..38,371] ~ overlap (+0.3%)
strings.js — Interp: 🔴 16, 3 unch. · avg -3.8% · Bytecode: 🟢 13, 🔴 1, 5 unch. · avg +3.1%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 394,600 ops/sec [392,344..397,374] → 386,404 ops/sec [382,840..389,442] 🔴 -2.1% 266,084 ops/sec [253,922..267,525] → 275,331 ops/sec [272,846..276,136] 🟢 +3.5%
template literal 778,412 ops/sec [763,164..795,310] → 784,504 ops/sec [781,497..787,845] ~ overlap (+0.8%) 462,330 ops/sec [461,391..464,194] → 477,152 ops/sec [470,193..487,424] 🟢 +3.2%
string repeat 405,735 ops/sec [402,618..408,517] → 385,738 ops/sec [384,019..386,584] 🔴 -4.9% 445,377 ops/sec [444,294..446,546] → 467,662 ops/sec [466,127..473,965] 🟢 +5.0%
split and join 137,049 ops/sec [135,151..137,938] → 134,511 ops/sec [133,705..135,338] ~ overlap (-1.9%) 141,535 ops/sec [141,061..143,093] → 151,781 ops/sec [148,434..153,817] 🟢 +7.2%
indexOf and includes 173,703 ops/sec [170,471..177,373] → 162,099 ops/sec [159,634..163,476] 🔴 -6.7% 171,924 ops/sec [170,736..175,087] → 180,701 ops/sec [179,552..182,326] 🟢 +5.1%
toUpperCase and toLowerCase 259,188 ops/sec [255,323..263,440] → 249,120 ops/sec [247,003..250,730] 🔴 -3.9% 323,432 ops/sec [319,224..324,331] → 326,440 ops/sec [324,318..328,299] ~ overlap (+0.9%)
slice and substring 156,411 ops/sec [150,451..158,728] → 148,892 ops/sec [147,437..149,333] 🔴 -4.8% 183,802 ops/sec [183,149..184,659] → 194,343 ops/sec [193,805..195,315] 🟢 +5.7%
trim operations 190,596 ops/sec [188,505..191,907] → 183,050 ops/sec [182,343..184,141] 🔴 -4.0% 234,425 ops/sec [233,661..234,831] → 236,680 ops/sec [235,915..237,631] 🟢 +1.0%
replace and replaceAll 209,164 ops/sec [206,922..211,436] → 199,445 ops/sec [195,829..201,639] 🔴 -4.6% 207,182 ops/sec [206,675..208,095] → 215,897 ops/sec [215,052..216,830] 🟢 +4.2%
startsWith and endsWith 138,969 ops/sec [138,610..139,342] → 130,143 ops/sec [129,835..130,906] 🔴 -6.4% 143,775 ops/sec [142,948..144,086] → 150,666 ops/sec [149,879..150,762] 🟢 +4.8%
padStart and padEnd 203,331 ops/sec [201,964..204,778] → 190,583 ops/sec [190,042..192,794] 🔴 -6.3% 211,712 ops/sec [210,357..212,325] → 223,875 ops/sec [220,583..225,654] 🟢 +5.7%
identity tag, no substitutions 487,737 ops/sec [483,162..489,969] → 464,859 ops/sec [461,151..467,893] 🔴 -4.7% 1,458,864 ops/sec [1,448,776..1,464,802] → 1,441,359 ops/sec [1,438,451..1,443,816] 🔴 -1.2%
tag with 1 substitution 89,038 ops/sec [88,780..91,335] → 86,685 ops/sec [86,314..87,393] 🔴 -2.6% 108,958 ops/sec [108,030..110,447] → 110,794 ops/sec [108,434..111,085] ~ overlap (+1.7%)
tag with 3 substitutions 48,734 ops/sec [48,352..49,413] → 47,764 ops/sec [47,251..48,011] 🔴 -2.0% 60,787 ops/sec [59,916..61,457] → 64,840 ops/sec [63,983..65,558] 🟢 +6.7%
tag with 6 substitutions 29,314 ops/sec [28,944..29,458] → 28,778 ops/sec [28,734..28,849] 🔴 -1.8% 38,794 ops/sec [38,514..39,168] → 39,030 ops/sec [38,263..39,540] ~ overlap (+0.6%)
String.raw, no substitutions 604,694 ops/sec [600,293..615,516] → 583,983 ops/sec [581,298..587,900] 🔴 -3.4% 548,471 ops/sec [547,226..550,372] → 567,622 ops/sec [563,692..573,144] 🟢 +3.5%
String.raw, 2 substitutions 430,387 ops/sec [420,802..439,190] → 416,157 ops/sec [406,756..424,862] ~ overlap (-3.3%) 388,348 ops/sec [384,912..392,134] → 401,833 ops/sec [399,815..409,009] 🟢 +3.5%
tag accessing .raw array 180,070 ops/sec [177,147..182,140] → 167,939 ops/sec [166,840..170,589] 🔴 -6.7% 202,560 ops/sec [200,410..215,209] → 202,746 ops/sec [201,519..203,240] ~ overlap (+0.1%)
method as tag (this binding) 65,822 ops/sec [65,240..66,255] → 63,295 ops/sec [63,101..63,794] 🔴 -3.8% 89,052 ops/sec [86,442..89,349] → 86,674 ops/sec [84,510..87,264] ~ overlap (-2.7%)
typed-arrays.js — Interp: 🟢 8, 🔴 8, 6 unch. · avg -0.8% · Bytecode: 🟢 11, 🔴 3, 8 unch. · avg +3.3%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 336,940 ops/sec [333,867..339,741] → 333,992 ops/sec [332,933..336,066] ~ overlap (-0.9%) 369,617 ops/sec [367,619..372,221] → 390,858 ops/sec [389,578..391,610] 🟢 +5.7%
new Int32Array(100) 300,592 ops/sec [297,068..303,143] → 302,727 ops/sec [300,581..304,223] ~ overlap (+0.7%) 330,906 ops/sec [324,764..335,798] → 346,794 ops/sec [345,524..347,945] 🟢 +4.8%
new Int32Array(1000) 173,021 ops/sec [171,961..174,594] → 185,493 ops/sec [183,992..186,035] 🟢 +7.2% 201,369 ops/sec [197,119..202,072] → 197,056 ops/sec [195,666..199,262] ~ overlap (-2.1%)
new Float64Array(100) 273,345 ops/sec [269,813..275,728] → 279,548 ops/sec [278,078..280,347] 🟢 +2.3% 318,465 ops/sec [313,690..321,004] → 312,903 ops/sec [310,603..315,734] ~ overlap (-1.7%)
Int32Array.from([...]) 178,777 ops/sec [177,517..179,282] → 182,876 ops/sec [181,931..183,344] 🟢 +2.3% 168,677 ops/sec [166,682..171,381] → 164,750 ops/sec [163,673..166,229] 🔴 -2.3%
Int32Array.of(1, 2, 3, 4, 5) 300,908 ops/sec [296,840..307,155] → 298,471 ops/sec [296,052..300,081] ~ overlap (-0.8%) 331,414 ops/sec [329,553..332,583] → 351,355 ops/sec [350,097..352,907] 🟢 +6.0%
sequential write 100 elements 3,533 ops/sec [3,465..3,556] → 3,490 ops/sec [3,462..3,497] ~ overlap (-1.2%) 11,256 ops/sec [11,214..11,300] → 16,489 ops/sec [16,345..16,555] 🟢 +46.5%
sequential read 100 elements 3,550 ops/sec [3,486..3,581] → 3,545 ops/sec [3,529..3,573] ~ overlap (-0.1%) 16,125 ops/sec [15,929..16,141] → 16,187 ops/sec [16,078..16,231] ~ overlap (+0.4%)
Float64Array write 100 elements 3,260 ops/sec [3,190..3,317] → 3,266 ops/sec [3,248..3,290] ~ overlap (+0.2%) 8,012 ops/sec [8,006..8,032] → 8,191 ops/sec [8,127..8,291] 🟢 +2.2%
fill(42) 47,240 ops/sec [47,066..47,266] → 48,287 ops/sec [48,255..48,347] 🟢 +2.2% 47,712 ops/sec [47,655..47,750] → 48,934 ops/sec [48,884..48,967] 🟢 +2.6%
slice() 193,341 ops/sec [192,047..194,807] → 200,034 ops/sec [196,777..202,370] 🟢 +3.5% 220,248 ops/sec [218,743..221,903] → 223,839 ops/sec [222,994..224,327] 🟢 +1.6%
map(x => x * 2) 8,100 ops/sec [8,058..8,114] → 7,582 ops/sec [7,429..7,650] 🔴 -6.4% 10,185 ops/sec [10,103..10,232] → 10,231 ops/sec [10,010..10,296] ~ overlap (+0.4%)
filter(x => x > 50) 8,565 ops/sec [8,508..8,568] → 7,873 ops/sec [7,792..7,903] 🔴 -8.1% 10,754 ops/sec [10,727..10,788] → 11,081 ops/sec [11,044..11,187] 🟢 +3.0%
reduce (sum) 8,072 ops/sec [7,797..8,174] → 7,659 ops/sec [7,575..7,698] 🔴 -5.1% 9,809 ops/sec [9,746..9,850] → 9,773 ops/sec [9,720..9,923] ~ overlap (-0.4%)
sort() 157,730 ops/sec [156,722..158,727] → 173,212 ops/sec [170,145..173,709] 🟢 +9.8% 182,308 ops/sec [181,234..182,598] → 175,151 ops/sec [174,894..175,317] 🔴 -3.9%
indexOf() 425,384 ops/sec [422,518..427,683] → 410,786 ops/sec [409,310..415,083] 🔴 -3.4% 483,944 ops/sec [482,424..486,819] → 508,736 ops/sec [508,150..509,442] 🟢 +5.1%
reverse() 311,768 ops/sec [311,198..312,447] → 321,998 ops/sec [318,589..322,762] 🟢 +3.3% 363,520 ops/sec [361,952..364,733] → 361,348 ops/sec [360,587..361,474] 🔴 -0.6%
create view over existing buffer 419,453 ops/sec [416,720..420,325] → 392,082 ops/sec [391,105..393,264] 🔴 -6.5% 460,233 ops/sec [459,586..461,304] → 469,794 ops/sec [469,240..473,486] 🟢 +2.1%
subarray() 436,153 ops/sec [420,953..437,787] → 414,797 ops/sec [405,199..416,562] 🔴 -4.9% 517,250 ops/sec [512,382..521,121] → 520,900 ops/sec [519,627..523,666] ~ overlap (+0.7%)
set() from array 603,373 ops/sec [593,487..606,900] → 543,669 ops/sec [540,026..548,361] 🔴 -9.9% 641,813 ops/sec [639,658..645,328] → 638,915 ops/sec [637,850..645,048] ~ overlap (-0.5%)
for-of loop 5,060 ops/sec [4,998..5,108] → 4,959 ops/sec [4,934..4,972] 🔴 -2.0% 29,303 ops/sec [29,254..29,385] → 29,983 ops/sec [29,928..30,088] 🟢 +2.3%
spread into array 16,570 ops/sec [16,488..16,674] → 16,721 ops/sec [16,680..16,730] 🟢 +0.9% 100,941 ops/sec [100,382..101,306] → 100,696 ops/sec [100,297..101,274] ~ overlap (-0.2%)
uint8array-encoding.js — Interp: 🟢 6, 🔴 9, 3 unch. · avg +0.6% · Bytecode: 🟢 7, 🔴 8, 3 unch. · avg -0.0%
Benchmark Interpreted Δ Bytecode Δ
short (5 bytes) 802,120 ops/sec [794,999..809,970] → 766,816 ops/sec [759,531..771,173] 🔴 -4.4% 1,159,983 ops/sec [1,153,162..1,166,529] → 1,122,210 ops/sec [1,114,481..1,131,759] 🔴 -3.3%
medium (450 bytes) 290,380 ops/sec [286,628..294,286] → 287,819 ops/sec [284,667..288,972] ~ overlap (-0.9%) 324,840 ops/sec [324,274..325,454] → 321,314 ops/sec [319,719..322,200] 🔴 -1.1%
large (4096 bytes) 46,159 ops/sec [44,077..46,654] → 45,562 ops/sec [43,660..46,018] ~ overlap (-1.3%) 47,367 ops/sec [44,202..47,722] → 44,842 ops/sec [44,723..47,411] ~ overlap (-5.3%)
base64url alphabet 204,755 ops/sec [202,668..208,009] → 199,581 ops/sec [198,823..202,504] 🔴 -2.5% 212,745 ops/sec [211,910..213,549] → 207,359 ops/sec [206,253..208,361] 🔴 -2.5%
omitPadding 371,354 ops/sec [369,103..373,877] → 366,352 ops/sec [362,856..368,912] 🔴 -1.3% 425,025 ops/sec [420,061..427,138] → 440,654 ops/sec [434,533..443,506] 🟢 +3.7%
short (8 chars) 329,087 ops/sec [316,798..330,159] → 335,424 ops/sec [330,358..338,684] 🟢 +1.9% 370,153 ops/sec [368,869..371,605] → 372,344 ops/sec [369,011..378,894] ~ overlap (+0.6%)
medium (600 chars) 143,850 ops/sec [142,660..144,689] → 141,814 ops/sec [140,706..141,971] 🔴 -1.4% 139,865 ops/sec [139,536..140,122] → 147,708 ops/sec [147,549..148,278] 🟢 +5.6%
large (5464 chars) 25,665 ops/sec [25,597..25,765] → 25,405 ops/sec [25,260..25,460] 🔴 -1.0% 26,371 ops/sec [26,335..26,460] → 26,085 ops/sec [24,633..26,259] 🔴 -1.1%
short (5 bytes) 822,463 ops/sec [817,954..828,804] → 799,040 ops/sec [792,171..816,990] 🔴 -2.8% 1,183,997 ops/sec [1,172,779..1,192,079] → 1,161,716 ops/sec [1,158,648..1,167,544] 🔴 -1.9%
medium (450 bytes) 260,229 ops/sec [258,759..260,921] → 270,946 ops/sec [270,072..272,232] 🟢 +4.1% 307,198 ops/sec [306,916..308,503] → 302,303 ops/sec [301,460..304,054] 🔴 -1.6%
large (4096 bytes) 39,401 ops/sec [37,802..39,603] → 42,158 ops/sec [40,861..42,749] 🟢 +7.0% 43,833 ops/sec [41,437..44,132] → 42,275 ops/sec [41,493..42,765] ~ overlap (-3.6%)
short (10 chars) 386,887 ops/sec [383,256..388,081] → 369,437 ops/sec [368,590..371,738] 🔴 -4.5% 403,873 ops/sec [402,905..405,794] → 416,153 ops/sec [413,825..422,126] 🟢 +3.0%
medium (900 chars) 220,412 ops/sec [219,189..222,295] → 231,104 ops/sec [229,932..232,856] 🟢 +4.9% 240,683 ops/sec [235,119..240,911] → 246,512 ops/sec [244,900..247,839] 🟢 +2.4%
large (8192 chars) 50,457 ops/sec [49,397..50,882] → 57,547 ops/sec [56,378..58,321] 🟢 +14.1% 61,099 ops/sec [60,803..61,281] → 58,061 ops/sec [57,434..58,200] 🔴 -5.0%
setFromBase64 (450 bytes) 138,020 ops/sec [136,454..139,847] → 130,557 ops/sec [129,887..132,012] 🔴 -5.4% 133,621 ops/sec [132,657..134,129] → 143,935 ops/sec [141,823..144,758] 🟢 +7.7%
setFromHex (450 bytes) 197,417 ops/sec [195,817..198,408] → 197,636 ops/sec [196,355..200,139] ~ overlap (+0.1%) 220,378 ops/sec [217,917..221,130] → 224,406 ops/sec [222,066..227,438] 🟢 +1.8%
toBase64 → fromBase64 (450 bytes) 101,786 ops/sec [100,805..102,558] → 100,428 ops/sec [99,688..100,777] 🔴 -1.3% 102,683 ops/sec [102,357..103,084] → 104,716 ops/sec [103,911..105,117] 🟢 +2.0%
toHex → fromHex (450 bytes) 126,967 ops/sec [124,898..128,689] → 133,241 ops/sec [131,491..135,278] 🟢 +4.9% 142,583 ops/sec [141,530..143,298] → 139,494 ops/sec [138,798..139,875] 🔴 -2.2%

Measured on ubuntu-latest x64. Benchmark ranges compare cached main-branch min/max ops/sec with the PR run; overlapping ranges are treated as unchanged noise. Percentage deltas are secondary context.

Add ES2025 spec section references to PathsShareBranch and
ResolveNamedBackreference per the project's annotation conventions.

Document why the alternation fallback (?:\1|\2) is correct: TRegExpr
fails (rather than matching empty) on backreferences to
non-participating groups, so the alternation falls through to the
participating group's backreference.

Add a test exercising \k<name> outside the disjunction containing the
duplicate groups to cover this path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frostney frostney merged commit f9877e9 into main Apr 12, 2026
9 checks passed
@frostney frostney deleted the t3code/duplicate-named-capture-groups branch April 12, 2026 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature New feature or request spec compliance Mismatch against official JavaScript/TypeScript specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant