Skip to content

perf: optimize Object.keys() checks and optional chaining#15865

Merged
vkarpov15 merged 8 commits into
masterfrom
perf/optimize-object-keys
Dec 21, 2025
Merged

perf: optimize Object.keys() checks and optional chaining#15865
vkarpov15 merged 8 commits into
masterfrom
perf/optimize-object-keys

Conversation

@AbdelrahmanHafez

@AbdelrahmanHafez AbdelrahmanHafez commented Dec 4, 2025

Copy link
Copy Markdown
Collaborator

Summary

  • Add utils.hasOwnKeys() helper that checks for empty objects without allocating an array via Object.keys()
  • Replace ~50 instances of Object.keys(obj).length pattern with hasOwnKeys(), reading the first key only instead of creating an array for all keys.
  • Expand optional chaining conversions across ~78 files, including:
    • x && x.yx?.y patterns
    • x != null && x.yx?.y patterns
    • x && x.y && x.zx?.y?.z deep access patterns
    • x != null ? x : defaultx ?? default
    • Remove redundant null checks before Array.isArray() calls (Array.isArray already returns false for null/undefined, and performs the same)
  • Cache repeated property lookups (e.g., this.$__schema.options, model.db.options)
  • Optimize indexOf() calls by caching the result instead of calling twice
  • Convert arguments to rest parameters in Query.prototype.slice()
  • Optimize Set operations in document.overwrite() and getModelsMapForPopulate()

@AbdelrahmanHafez AbdelrahmanHafez marked this pull request as draft December 4, 2025 02:24

Copilot AI left a comment

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.

Pull request overview

This PR implements several performance optimizations across the Mongoose codebase:

  • Introduces a new utils.hasOwnKeys() helper that checks for object keys more efficiently than Object.keys().length
  • Converts verbose property access chains to optional chaining syntax
  • Caches repeated property lookups to avoid redundant access
  • Optimizes indexOf() calls by caching results
  • Converts arguments object usage to rest parameters
  • Optimizes Set operations for duplicate checking

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated no comments.

Show a summary per file
File Description
lib/utils.js Adds hasOwnKeys() helper function and updates isEmptyObject() to use it
lib/types/subdocument.js Replaces Object.keys(ret).length === 0 with hasOwnKeys()
lib/types/documentArray/methods/index.js Converts property access chains to optional chaining
lib/types/documentArray/index.js Converts property access chains to optional chaining
lib/types/array/methods/index.js Replaces Object.keys() length checks with hasOwnKeys()
lib/types/array/index.js Converts property access chains to optional chaining
lib/schemaType.js Simplifies null/undefined check to != null
lib/schema/subdocument.js Converts to optional chaining and uses hasOwnKeys()
lib/schema/string.js Simplifies null/undefined checks to != null
lib/schema/number.js Simplifies null/undefined checks to != null
lib/schema/mixed.js Replaces Object.keys() check with hasOwnKeys()
lib/schema/map.js Replaces Object.keys() check with hasOwnKeys()
lib/schema/array.js Converts property access chain to optional chaining
lib/schema.js Converts to optional chaining and uses hasOwnKeys()
lib/queryHelpers.js Converts to optional chaining
lib/query.js Caches repeated property access, converts to rest parameters, uses hasOwnKeys(), and optional chaining
lib/plugins/trackTransaction.js Replaces Object.keys() checks with hasOwnKeys()
lib/mongoose.js Converts to optional chaining and uses hasOwnKeys()
lib/model.js Converts to optional chaining and uses hasOwnKeys()
lib/helpers/update/castArrayFilters.js Adds utils import and uses hasOwnKeys()
lib/helpers/update/applyTimestampsToUpdate.js Adds utils import and uses hasOwnKeys()
lib/helpers/schema/getIndexes.js Simplifies null/undefined check to != null
lib/helpers/schema/applyWriteConcern.js Adds utils import, uses optional chaining and hasOwnKeys()
lib/helpers/schema/applyReadConcern.js Converts to optional chaining
lib/helpers/query/castUpdate.js Uses hasOwnKeys() for empty object checks
lib/helpers/populate/getModelsMapForPopulate.js Optimizes duplicate checking with Set, uses optional chaining and hasOwnKeys()
lib/helpers/document/compile.js Converts to optional chaining and uses hasOwnKeys()
lib/helpers/document/applyVirtuals.js Adds utils import and uses hasOwnKeys()
lib/helpers/document/applyTimestamps.js Adds utils import and uses hasOwnKeys()
lib/drivers/node-mongodb-native/connection.js Converts to optional chaining and uses hasOwnKeys()
lib/drivers/node-mongodb-native/collection.js Converts to optional chaining
lib/document.js Optimizes with Set for key deduplication, caches schema options, uses hasOwnKeys()
lib/cursor/changeStream.js Converts to optional chaining
lib/connection.js Caches indexOf() results and uses hasOwnKeys()
lib/cast.js Converts to optional chaining and uses hasOwnKeys()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@vkarpov15 vkarpov15 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Some of the changes look slower than what was originally there, most notably optional chaining and checking if Object.keys(obj).length.

Comment thread lib/utils.js
Comment thread lib/cast.js
Comment thread lib/connection.js
Comment thread lib/document.js
@AbdelrahmanHafez AbdelrahmanHafez marked this pull request as draft December 20, 2025 15:17
@AbdelrahmanHafez

Copy link
Copy Markdown
Collaborator Author

Changing this to draft since I found a bunch of other opportunities to refactor for optional chaining and simplify code in general.

@AbdelrahmanHafez AbdelrahmanHafez added the refactor This is a purely internal change for making Mongoose internals easier to understand and work with label Dec 21, 2025

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 83 out of 83 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/schema/buffer.js
Comment thread lib/schema/buffer.js
Comment thread lib/helpers/populate/getModelsMapForPopulate.js Outdated
Comment thread lib/schema/union.js
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@AbdelrahmanHafez

Copy link
Copy Markdown
Collaborator Author

@vkarpov15 This PR grew a bit larger than I initially intended, there were many places where optional chaining could improve readability and/or performance. I've reviewed it a couple of times myself, had Copilot validate it as well, and all tests and lint are passing, which should give us good confidence in these changes.

That said, if it would make things easier for you, I'm happy to split this into smaller PRs. Let me know if there's anything I can do to make the review process smoother.

@AbdelrahmanHafez AbdelrahmanHafez marked this pull request as ready for review December 21, 2025 16:35
@vkarpov15

Copy link
Copy Markdown
Collaborator

This PR's great, no need for separate PRs. Thanks 👍

@vkarpov15 vkarpov15 merged commit 4c13300 into master Dec 21, 2025
50 checks passed
@vkarpov15 vkarpov15 added this to the 9.0.3 milestone Dec 21, 2025
@hasezoey hasezoey deleted the perf/optimize-object-keys branch December 22, 2025 11:53
@vkarpov15 vkarpov15 modified the milestones: 9.0.3, 9.1 Dec 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance refactor This is a purely internal change for making Mongoose internals easier to understand and work with

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants