Skip to content

Performance Fix: Solving Quadratic Slowdown in svelte-table#6266

Open
Abyanzhafran wants to merge 3 commits into
TanStack:alphafrom
Abyanzhafran:fix/svelte-table
Open

Performance Fix: Solving Quadratic Slowdown in svelte-table#6266
Abyanzhafran wants to merge 3 commits into
TanStack:alphafrom
Abyanzhafran:fix/svelte-table

Conversation

@Abyanzhafran
Copy link
Copy Markdown

@Abyanzhafran Abyanzhafran commented May 16, 2026

🎯 Changes

Fixes #6235

Performance Fix: Solving Quadratic Slowdown in svelte-table

Root Cause Analysis (RCA)

The performance degradation observed in the Svelte 5 adapter was caused by a nested getter chain created during rapid reactive updates.

  1. The Mechanism: The mergeObjects utility (inherited from SolidJS patterns) creates a proxy-like object using lazy getters. These getters do not resolve values eagerly; instead, they walk through a sources array via a closure every time a property is read.
  2. The Loop: In createTable.svelte.ts, the $effect.pre block calls setOptions on every reactive tick (e.g., every keystroke in a filter input). Each call to setOptions wraps the previous options object in a new layer of mergeObjects.
  3. The Bottleneck: After N reactive updates, table.options becomes an N-deep chain of closures. Reading a value that lives in the base defaults (like feature configurations) requires traversing all N layers.
  4. Complexity: Since the row-model pipeline reads these options multiple times per row per tick, the aggregate work scales at O(N²), leading to the reported UI freezes during rapid input.

The Solution: Eager Resolution with flatMerge

Introduced a new utility, flatMerge, to break the recursion chain in hot reactive paths.

flatMerge vs mergeObjects

  • mergeObjects (Lazy): Preserves getters. Great for initial setup where you want to maintain a "live" link to reactive state.
  • flatMerge (Eager): Resolves all values immediately and copies them into a plain, flat JavaScript object. This transforms the lookup complexity from O(N) back to O(1).

Implementation Strategy

I implemented a targeted fix that preserves reactivity while eliminating the slowdown:

  1. Preserved Initialization: mergeObjects is still used during the initial table setup to ensure the adapter correctly "captures" Svelte's reactive getters.
  2. Flattened Updates: Inside the $effect.pre loop and the mergeOptions handler, we switched to flatMerge.
    • Since $effect.pre re-runs automatically when dependencies change, we don't need the resulting object to have getters; the effect itself acts as the "pump" that pushes fresh, flat data into the table core.

Performance Impact

  • Before: Reading a default option after 50 keystrokes = 50 nested function calls per read. Aggregate work is quadratic.
  • After: Reading a default option after 50 keystrokes = 1 direct property access. Aggregate work is linear/constant relative to state updates.

Code Summary

// New utility to prevent getter-chain accumulation
export function flatMerge(...sources: any): any {
  const result = {}
  for (let source of sources) {
    if (typeof source === 'function') source = source()
    if (!source) continue
    for (const key of Reflect.ownKeys(source)) {
      const value = source[key]
      if (value !== undefined) {
        result[key] = value
      }
    }
  }
  return result
}

This fix ensures @tanstack/svelte-table remains performant even in complex tables with rapid state changes, while maintaining full compatibility with Svelte 5's reactivity model.

How to reproduce in our local env

  • Clone this updated solution
  • Use pnpm link to connect into your reproducible project

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

Summary by CodeRabbit

  • Refactor
    • Changed how table options are resolved and merged during table initialization and updates.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

📝 Walkthrough

Walkthrough

The PR introduces a new flatMerge utility function to the Svelte table package that eagerly resolves sources and merges enumerable properties as plain data values, then replaces two mergeObjects call sites in table creation to apply this new merging strategy during option resolution and updates.

Changes

Option Merging with flatMerge Utility

Layer / File(s) Summary
flatMerge utility implementation
packages/svelte-table/src/merge-objects.ts
flatMerge function with TypeScript overloads is added. It resolves function sources eagerly, copies enumerable own keys into a result object, allows later sources to override earlier ones, and skips undefined values without preserving getters.
Integration in createTable
packages/svelte-table/src/createTable.svelte.ts
Import statement expands to include flatMerge. The mergeOptions resolver and the reactive table.setOptions updater both switch from mergeObjects to flatMerge to change how default, user, and incremental options are combined.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A flatMerge hops in place,
Eager values win the race,
Getters fade, the data stays,
Options merge in simpler ways! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. 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 directly addresses the main change: replacing mergeObjects with flatMerge to eliminate quadratic slowdown caused by nested getter chains in reactive updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

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.

svelte-table: nested mergeObjects in setOptions produces quadratic slowdown on rapid state changes

1 participant