Sitelet https://github.com/calcom/cal.diy/pull/30020
Skip to content

perf(app-store): dedupe location options with a Set instead of a linear scan - #30020

Open
Chirag6722 wants to merge 1 commit into
calcom:mainfrom
Chirag6722:fix/29959-location-options-dedupe
Open

perf(app-store): dedupe location options with a Set instead of a linear scan#30020
Chirag6722 wants to merge 1 commit into
calcom:mainfrom
Chirag6722:fix/29959-location-options-dedupe

Conversation

@Chirag6722

Copy link
Copy Markdown

What is the problem

getLocationGroupedOptions deduped location options with a linear scan, and then rebuilt the array it was scanning:

if (apps[groupByCategory]) {
  const existingOption = apps[groupByCategory].find((o) => o.value === option.value);
  if (!existingOption) {
    apps[groupByCategory] = [...apps[groupByCategory], option];
  }
}

Both halves are O(n) per credential, so the loop is quadratic in the number of installed apps. #29959 reports the .find(); the spread is a second, independent O(n) that also copies the whole array on every insert.

The fix

Keep a Set of the values already collected per category and push onto the existing array, so each insert is O(1).

Semantics are deliberately unchanged: the first option for a given value still wins, and insertion order is preserved.

Verification

The requirement in the issue is that output stays identical, so I checked that directly rather than reasoning about it. Both algorithms were run over 400 randomised inputs (up to 60 entries, 4 categories, 12 distinct values, so duplicates are frequent) and compared by deep equality:

random trials: 400, output mismatches: 0

Timing the two on a single category shows the change in growth:

n=1000  old=  12.1ms  new= 0.1ms
n=4000  old=  63.3ms  new= 0.5ms
n=8000  old= 339.7ms  new= 2.1ms

The old path grows faster than linearly as n doubles; the new one does not. At 8000 options that is 339.7 ms to 2.1 ms.

Those numbers come from a standalone harness reproducing both variants of the dedupe, not from the app, because reaching this function requires Prisma plus getEnabledAppsFromCredentials. I have not added a Vitest case for the same reason: there is currently no test covering getLocationGroupedOptions, and the mocking needed to reach it is considerably larger than this diff. Happy to add one if you would rather the dedupe contract were pinned in the suite.

Scope

One file, +12/-4.

defaultLocations.forEach just below uses the same spread-to-append pattern, and I left it alone on purpose: it iterates a fixed, small list rather than user credentials, so it is not quadratic in anything an account controls, and changing it would add diff without addressing the issue. Say the word if you would like it converted for consistency.

Closes #29959

…ar scan

getLocationGroupedOptions deduped each location option by scanning the
category array with .find(), then rebuilt that array with a spread. Both
are O(n) per credential, so building the options was quadratic in the
number of installed apps.

Tracks the values already collected per category in a Set and pushes onto
the existing array, making each insert O(1). Output is unchanged: the
first option for a value still wins and insertion order is preserved.

Closes calcom#29959
@github-actions

Copy link
Copy Markdown
Contributor

Welcome to Cal.diy, @Chirag6722! Thanks for opening this pull request.

A few things to keep in mind:

  • This is Cal.diy, not Cal.com. Cal.diy is a community-driven, fully open-source fork of Cal.com licensed under MIT. Your changes here will be part of Cal.diy — they will not be deployed to the Cal.com production app.
  • Please review our Contributing Guidelines if you haven't already.
  • Make sure your PR title follows the Conventional Commits format.

A maintainer will review your PR soon. Thanks for contributing!

@Chirag6722

Copy link
Copy Markdown
Author

Marked ready for review — the change is complete, not a work in progress. I opened it as a draft because AGENTS.md says to default to draft, but that appears aimed at work happening inside the repo: as an external PR it meant CodeRabbit skipped it, and CI cannot start anyway until someone adds run-ci.

On that: the only red check is the external-contributor gate, which fails in 3s and skips everything downstream.

This PR is from an external contributor and requires the 'run-ci' label before CI can run.

So Tests, Type Checks, Linters and Production builds have not actually run yet. Whenever a maintainer is happy to add the label, they will.

For what it is worth on review effort: the diff is +12/-4 in one file, and the verification is in the description — 400 randomised inputs comparing the old and new dedupe by deep equality with 0 mismatches, plus timings showing 339.7ms to 2.1ms at n=8000.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 19c4dc02-311d-4bdc-9ab2-3d36e60b7989

📥 Commits

Reviewing files that changed from the base of the PR and between 176037d and f2d7250.

📒 Files selected for processing (1)
  • packages/app-store/server.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

getLocationGroupedOptions now maintains a Set of option values for each category. It uses set membership for duplicate detection and appends new options directly to category arrays. Category initialization creates both the option array and its value set.

Merge Risk: ⚪ Minimal · up to f2d72

This change improves location-option deduplication performance while preserving output order and first-match behavior; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Set-based deduplication performance improvement in the app-store location options code.
Description check ✅ Passed The description explains the performance problem, the implementation, preserved semantics, verification, and scope.
Linked Issues check ✅ Passed The change satisfies [#29959] by using per-category Set membership and preserving output order and first-value precedence.
Out of Scope Changes check ✅ Passed The changes are limited to the targeted deduplication path and do not introduce unrelated code changes.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@CLAassistant

CLAassistant commented Aug 21, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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

Labels

🐛 bug Something isn't working size/S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance: repeated .find() calls cause O(n²) when building app location options in packages/app-store/server.ts

2 participants