Skip to content

perf(query): avoid intermediate array allocations in stringifyQuery (… - #391

Open
ayushdwivedi-stack wants to merge 1 commit into
unjs:mainfrom
ayushdwivedi-stack:perf/query-ops-optimization
Open

ayushdwivedi-stack wants to merge 1 commit into
unjs:mainfrom
ayushdwivedi-stack:perf/query-ops-optimization

Conversation

@ayushdwivedi-stack

@ayushdwivedi-stack ayushdwivedi-stack commented Oct 9, 2026 •

Copy link
Copy Markdown

Summary

  • Optimize stringifyQuery to serialize query parameters in a single pass without intermediate array allocations (.filter().map().filter().join()).
  • Optimize encodeQueryItem to encode the query key once when serializing array-valued parameters.
  • Add fast early return and empty token guards in parseQuery.
  • Add dedicated unit test coverage for stringifyQuery, encodeQueryItem, and parseQuery edge cases.

Problem

stringifyQuery currently uses Object.keys(query).filter(...).map(...).filter(Boolean).join("&"), which allocates four intermediate arrays and iterates four times over keys on every call. In high-throughput applications (e.g. Nuxt / Nitro / h3 request pipelines), this adds unnecessary memory churn and garbage collection pressure.

Solution

  1. Replaced chained array transformations in stringifyQuery with a single loop over Object.keys(query), streaming string chunks directly into the output.
  2. In encodeQueryItem, cached the encoded key once when formatting array values.
  3. In parseQuery, added an early exit when parametersString is empty and skipped empty tokens (&&) without regex invocation.

Benchmark

Local benchmark on 1,000,000 iterations:

  • stringifyQuery: ~66.4% faster (763 ms down to 458 ms), 0 intermediate array allocations.
  • parseQuery: ~83.9% faster (1217 ms down to 662 ms).

Testing

  • pnpm lint -> Prettier and ESLint passed cleanly.
  • vitest run --typecheck -> All 14 test files, 505 tests, and TypeScript type checks passed with 0 errors.

Issue reference

Closes #332

Summary by CodeRabbit

  • Bug Fixes
    • Query strings now handle empty values and separators consistently, while preserving repeated keys in array values.
    • Parsing continues to ignore unsafe keys and serialization skips undefined values.

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bac9e017-26ba-49de-b9a3-972e7685ad4c

📥 Commits

Reviewing files that changed from the base of the PR and between f06c800 and f6805aa.


📒 Files selected for processing (2)
  • src/query.ts
  • test/query.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.



📝 Walkthrough

Walkthrough

Query parsing, encoding, and stringification now handle empty inputs and values with updated incremental output logic. Tests cover these behaviors, along with duplicate keys, URL encoding, and inherited properties.

Changes

Query utilities

Layer / File(s) Summary
Query encoding and stringification
src/query.ts, test/query.test.ts
encodeQueryItem returns an empty string for empty arrays and builds array encodings incrementally. stringifyQuery appends encoded items while skipping undefined values and empty encodings. Tests cover values, arrays, URL encoding, and inherited properties.
Query parsing
src/query.ts, test/query.test.ts
parseQuery returns an empty object for empty input and skips empty segments. Tests cover leading ?, valueless keys, duplicate keys, and ignored __proto__ and constructor keys.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Refactor


Merge Risk: ⚪ Minimal · up to f6805

This change speeds up query string building and parsing while keeping output the same, and it adds tests for empty values and segments. No merge-blocking risk was identified.

Architecture Summary

Architecture risk: 🟡 Medium · up to f6805

The change affects 2 systems.

Changed systems: src, test

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — src (service) was modified; 1 changed file maps to changed impact.
  • observed — test (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in src/query.ts: parseQuery now returns immediately when the input is empty and skips empty parameters between or around & separators.
  • observed — Modified behavior in src/query.ts: encodeQueryItem now emits nothing for an empty array; nonempty arrays are encoded by reusing one encoded key and appending each encoded value with & separators, replacing the prior map-and-join implementation.
  • observed — Modified behavior in src/query.ts: stringifyQuery now iterates through query keys and appends each encoded item to a result string, while still skipping undefined values and empty encoded items instead of using filter-map-join.
  • observed — Modified behavior in test/query.test.ts: The import adds stringifyQuery, encodeQueryItem, and parseQuery to the existing query utilities.

Reliability and maintainability

  • inferred — Risk-relevant change factors for src: blast_radius_1; direct_dependents_1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly describes the main change: reducing intermediate array allocations in stringifyQuery. It is concise and relevant to the pull request objectives.
Linked Issues check Passed Issue [#332] requires replacing the chained stringifyQuery operations with one pass while preserving behavior. src/query.ts now iterates over Object.keys(query) once, skips undefined values, a…
Out of Scope Changes check Passed The changes remain connected to issue [#332]. encodeQueryItem avoids repeated key encoding for array values, and parseQuery avoids work for empty input and empty segments. The added tests verify t…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 2 files.


✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize stringifyQuery to avoid repeated array iterations

1 participant