Skip to content

Fix phone validation performance by using Set/Map instead of Array lookups#17843

Merged
FelixMalfait merged 1 commit intomainfrom
fix/phone-validation-perf
Feb 10, 2026
Merged

Fix phone validation performance by using Set/Map instead of Array lookups#17843
FelixMalfait merged 1 commit intomainfrom
fix/phone-validation-perf

Conversation

@FelixMalfait
Copy link
Copy Markdown
Member

Summary

  • isValidCountryCode was using Array.includes() on ~250 country codes (O(n) per call). Replaced with a Set.has() lookup (O(1)).
  • getCountryCodesForCallingCode was iterating all ~250 countries and calling getCountryCallingCode() on each one every invocation. Replaced with a precomputed Map<callingCode, CountryCode[]> built once at module load (O(1) per call).

Both functions are called from transformPhonesValue on every phone field mutation, causing cumulative overhead visible in profiling (p95 self-time ~20-35ms).

Test plan

  • Verify phone field creation/update still works correctly (country code validation, calling code resolution)
  • Verify spreadsheet import with phone fields still validates properly
  • Confirm no regression in isValidCountryCode or getCountryCodesForCallingCode behavior

Made with Cursor

…okups

`isValidCountryCode` was using `Array.includes()` (O(n)) on every call.
`getCountryCodesForCallingCode` was iterating all ~250 countries and calling
`getCountryCallingCode()` on each one per invocation. Both are now O(1) by
precomputing a Set and a Map at module load time.

Co-authored-by: Cursor <cursoragent@cursor.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 10, 2026

Greptile Overview

Greptile Summary

This PR optimizes phone validation performance by replacing expensive O(n) array operations with O(1) data structure lookups. Both isValidCountryCode and getCountryCodesForCallingCode are called from transformPhonesValue on every phone field mutation, making this optimization impactful.

Changes:

  • isValidCountryCode: Changed from Array.includes() to Set.has() for O(1) country code validation
  • getCountryCodesForCallingCode: Replaced runtime filtering of ~250 countries with a precomputed Map<callingCode, CountryCode[]> built at module load time

The optimization is sound and maintains behavioral equivalence. The precomputed map approach is appropriate since country calling codes are static data that doesn't change at runtime.

Confidence Score: 5/5

  • This PR is safe to merge with no issues found
  • The changes are well-implemented performance optimizations that maintain behavioral equivalence. The use of Set and Map for constant-time lookups is the correct approach for this use case. The code follows TypeScript best practices with proper type annotations, and the logic is straightforward and correct.
  • No files require special attention

Important Files Changed

Filename Overview
packages/twenty-shared/src/utils/validation/phones-value/isValidCountryCode.ts Optimized country code validation from O(n) Array.includes() to O(1) Set.has() lookup by using Set data structure
packages/twenty-shared/src/utils/validation/phones-value/getCountryCodesForCallingCode.ts Replaced O(n) filter operation with O(1) Map lookup by precomputing calling code to country mapping at module load time

Sequence Diagram

sequenceDiagram
    participant Client
    participant transformPhonesValue
    participant isValidCountryCode
    participant getCountryCodesForCallingCode
    participant Set/Map

    Client->>transformPhonesValue: phone field mutation
    transformPhonesValue->>isValidCountryCode: validate countryCode
    isValidCountryCode->>Set/Map: Set.has(countryCode) - O(1)
    Set/Map-->>isValidCountryCode: boolean
    isValidCountryCode-->>transformPhonesValue: validation result
    
    transformPhonesValue->>getCountryCodesForCallingCode: validate callingCode
    getCountryCodesForCallingCode->>Set/Map: Map.get(callingCode) - O(1)
    Set/Map-->>getCountryCodesForCallingCode: CountryCode[]
    getCountryCodesForCallingCode-->>transformPhonesValue: country codes
    
    transformPhonesValue-->>Client: validated phone data
Loading

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-shared/src/utils/validation/phones-value/getCountryCodesForCallingCode.ts">

<violation number="1" location="packages/twenty-shared/src/utils/validation/phones-value/getCountryCodesForCallingCode.ts:29">
P2: Returning the cached array exposes internal mutable state; callers can mutate it and corrupt future lookups. Return a copy to preserve previous behavior of returning a fresh array.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


return countryCallingCode === cleanCallingCode;
});
return CALLING_CODE_TO_COUNTRIES.get(cleanCallingCode) ?? [];
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

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

P2: Returning the cached array exposes internal mutable state; callers can mutate it and corrupt future lookups. Return a copy to preserve previous behavior of returning a fresh array.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-shared/src/utils/validation/phones-value/getCountryCodesForCallingCode.ts, line 29:

<comment>Returning the cached array exposes internal mutable state; callers can mutate it and corrupt future lookups. Return a copy to preserve previous behavior of returning a fresh array.</comment>

<file context>
@@ -1,15 +1,30 @@
-
-    return countryCallingCode === cleanCallingCode;
-  });
+  return CALLING_CODE_TO_COUNTRIES.get(cleanCallingCode) ?? [];
 };
</file context>
Suggested change
return CALLING_CODE_TO_COUNTRIES.get(cleanCallingCode) ?? [];
return [...(CALLING_CODE_TO_COUNTRIES.get(cleanCallingCode) ?? [])];
Fix with Cubic

Copy link
Copy Markdown
Member

@Weiko Weiko left a comment

Choose a reason for hiding this comment

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

LGTM

@FelixMalfait FelixMalfait added this pull request to the merge queue Feb 10, 2026
Merged via the queue into main with commit 9c984b1 Feb 10, 2026
84 checks passed
@FelixMalfait FelixMalfait deleted the fix/phone-validation-perf branch February 10, 2026 20:15
@twenty-eng-sync
Copy link
Copy Markdown

Hey @FelixMalfait! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

1 similar comment
@twenty-eng-sync
Copy link
Copy Markdown

Hey @FelixMalfait! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

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.

2 participants