惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
aimingoo的专栏
aimingoo的专栏
Microsoft Security Blog
Microsoft Security Blog
NISL@THU
NISL@THU
T
Threatpost
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs
S
Securelist
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
人人都是产品经理
人人都是产品经理
B
Blog RSS Feed
S
Secure Thoughts
MyScale Blog
MyScale Blog
O
OpenAI News
P
Palo Alto Networks Blog
美团技术团队
C
Cyber Attacks, Cyber Crime and Cyber Security
TaoSecurity Blog
TaoSecurity Blog
量子位
L
Lohrmann on Cybersecurity
G
GRAHAM CLULEY
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Tailwind CSS Blog
Know Your Adversary
Know Your Adversary
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Simon Willison's Weblog
Simon Willison's Weblog
宝玉的分享
宝玉的分享
PCI Perspectives
PCI Perspectives
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tenable Blog
I
InfoQ
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Microsoft Azure Blog
Microsoft Azure Blog
Recent Announcements
Recent Announcements
S
Security @ Cisco Blogs
S
Schneier on Security
B
Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
The Cloudflare Blog
AWS News Blog
AWS News Blog
IT之家
IT之家
V
Vulnerabilities – Threatpost
The Hacker News
The Hacker News
H
Heimdal Security Blog
I
Intezer
A
Arctic Wolf
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Help Net Security
W
WeLiveSecurity

TanStack Blog

Run Any Coding Agent in a Sandbox, With One chat() Call | TanStack Blog TanStack Start and TanStack AI Win 2026 Open Source Awards | TanStack Blog TypeScript Performance in TanStack Table V9 | TanStack Blog TanStack AI Beta: The Switzerland of AI Tooling Grows Up | TanStack Blog TanStack Table V9: Taking Form | TanStack Blog TanStack AI: Your MCP, your way | TanStack Blog TanStack Start Adds First-Class Rsbuild Support | TanStack Blog Introducing Experimental Workflows and Orchestrators in TanStack AI | TanStack Blog Chat UIs Are Lists Until They Aren't | TanStack Blog Structured Output That Remembers Across Turns | TanStack Blog TanStack Virtual just got a lot faster, and finally handles iOS | TanStack Blog TanStack AI now fully speaks AG-UI | TanStack Blog Stop Waiting on JSON: Stream Structured Output with One Schema | TanStack Blog Hardening TanStack After the npm Compromise | TanStack Blog Postmortem: TanStack npm supply-chain compromise | TanStack Blog Who Owns the Tree? RSC as a Protocol, Not an Architecture | TanStack Blog TanStack AI Just Learned to Compose Music | TanStack Blog Your AI Tool Calls Should Fail at Compile Time, Not in Production | TanStack Blog One Flag, Every Chunk: Debug Logging Lands in TanStack AI | TanStack Blog How We Test TanStack AI Across 7 Providers on Every PR | TanStack Blog React Server Components Your Way | TanStack Blog Code Mode: Let Your AI Write Programs, Not Just Call Tools | TanStack Blog Solid 2.0 Beta Support in TanStack Router, Start, and Query | TanStack Blog TanStack Router's New Reactive Core: A Signal Graph | TanStack Blog TanStack DB 0.6 Now Includes Persistence, Offline Support, and Hierarchical Data | TanStack Blog We Removed 3rd Party Ads from TanStack.com | TanStack Blog 5x SSR Throughput: Profiling SSR Hot Paths in TanStack Start | TanStack Blog Lazy Tool Discovery: Scaling AI Tool Systems Without Drowning in Tokens | TanStack Blog TanStack AI Just Got Middleware — And It Changes Everything | TanStack Blog Talk to Your AI: Realtime Voice Chat in TanStack AI | TanStack Blog Generation Hooks: Type-Safe AI Beyond Chat | TanStack Blog TanStack + OpenRouter Partnership | TanStack Blog Introducing TanStack Intent: Ship Agent Skills with your npm Packages | TanStack Blog TanStack AI: Why We Split the Adapters | TanStack Blog The ai() Function That Almost Was | TanStack Blog TanStack AI Alpha 2: Every Modality, Better APIs, Smaller Bundles | TanStack Blog TanStack AI Alpha: Your AI, Your Way | TanStack Blog How We Track Billions of Downloads: The NPM Stats API Deep Dive | TanStack Blog The State of TanStack, Two Years of Full-Time OSS | TanStack Blog How we accidentally made route matching more performant by aiming for correctness | TanStack Blog TanStack DB 0.5 . Query-Driven Sync | TanStack Blog Directives and the Platform Boundary | TanStack Blog TanStack Start v1 Release Candidate | TanStack Blog Stop Re-Rendering. TanStack DB, the Embedded Client Database for TanStack Query | TanStack Blog Search Params Are State | TanStack Blog The Power in Pragmatism | TanStack Blog TanStack + Netlify Partnership | TanStack Blog Announcing TanStack Form v1 | TanStack Blog Why choose TanStack Start and Router? | TanStack Blog Why TanStack Start is Ditching Adapters | TanStack Blog A milestone for TypeScript Performance in TanStack Router | TanStack Blog Announcing TanStack Query v5 | TanStack Blog Announcing TanStack Query v4 | TanStack Blog TanStack Table + Ag-Grid Partnership | TanStack Blog
How an Underrated Refactor Saved 90% Memory Usage | TanStack Blog
Kevin Van Cott · 2026-06-22 · via TanStack Blog

by Kevin Van Cott on Jun 22, 2026.

TanStack Table V9 - Memory Performance

Up to 90% less memory usage in TanStack Table V9 compared to TanStack Table V8? Yes! For large tables at least.

How did we achieve this? Or how was there this much room to improve to begin with?

TanStack Table V9 has a lot of architectural changes for the better: a state management system overhaul and a new feature/plugin system with a much smaller "only pay for what you use" runtime model.

But one of the largest performance wins during the development of Table V9 came from a more subtle behind-the-scenes refactor. This refactor resulted in TanStack Table V9 using up to ~90% less memory than Table V8 for large tables when needing to process hundreds of thousands or millions of rows, either paginated or virtualized.

This improvement is either a big deal or one that barely matters, depending on how you want to use TanStack Table. In Table V8, depending on a large number of factors, you could only expect TanStack Table to be able to handle about 1 million (1.5 million at most) rows before running into memory issues in the browser (which is often the 4GB mark). With Table V9, the maximum number of rows that TanStack Table can handle before using 4GB of memory is now 10-16 million rows according to our benchmarks. Though note, that depending on how complex your web page is, you will most likely run into issues below these optimistic numbers, but this is up to a 10x improvement in the scalability of TanStack Table itself.

Should you be fetching or otherwise asking TanStack Table to handle 15 million rows client-side in the browser? Well, usually not, though we have seen use-cases that are not that "far-fetched". 😉

In this article, we'll go over our benchmark results, and then the details of how we achieved this performance improvement. It was surprisingly a simple refactor that had almost no downsides (besides 1 breaking change). There are probably many libraries out there that could be using this same pattern to improve their own memory usage, if they are not already.

chart showing the memory usage of TanStack Table V8 and Table V9 across paginated rows, virtualized rows, virtualized columns, and kitchen sink examples

The charts above show that as the number of cells (rows x columns) that TanStack Table is asked to process (not just render) grows, the memory usage differences between Table V8 and Table V9 become much more pronounced. On the left of the graph, the difference is negligible, but on the right, Table V9 uses more than 2.4 GB less retained JS heap than Table V8 when processing 1 million rows x 8 columns.

Here are the full benchmark results:

Benchmark Example# of Cells (rows x columns)Table V8 Memory UsedTable V9 Memory UsedMemory SavedPercentage Improvement
paginated rows80 (10 x 8)1.93 MB1.91 MB0.02 MB1.0%
paginated rows8,000 (1,000 x 8)4.71 MB2.22 MB2.49 MB52.9%
paginated rows800,000 (100,000 x 8)272.58 MB27.28 MB245.30 MB90.0%
paginated rows8,000,000 (1,000,000 x 8)2710.06 MB257.19 MB2452.87 MB90.5%
virtualized rows80 (10 x 8)2.13 MB2.10 MB0.03 MB1.4%
virtualized rows8,000 (1,000 x 8)5.16 MB2.68 MB2.48 MB48.1%
virtualized rows800,000 (100,000 x 8)273.42 MB28.12 MB245.30 MB89.7%
virtualized rows8,000,000 (1,000,000 x 8)2714.32 MB261.46 MB2452.86 MB90.4%
virtualized columns100 (10 x 10)2.24 MB2.24 MB0.00 MB0.0%
virtualized columns10,000 (100 x 100)5.31 MB3.83 MB1.48 MB27.9%
virtualized columns100,000 (100 x 1,000)25.82 MB10.73 MB15.09 MB58.4%
virtualized columns1,000,000 (100 x 10,000)230.47 MB80.24 MB150.23 MB65.2%
kitchen sink80 (10 x 8)2.18 MB2.38 MB-0.20 MB-9.2%
kitchen sink8,000 (1,000 x 8)4.96 MB2.79 MB2.17 MB43.8%
kitchen sink800,000 (100,000 x 8)272.83 MB36.91 MB235.92 MB86.5%
kitchen sink8,000,000 (1,000,000 x 8)2710.31 MB349.22 MB2361.09 MB87.1%

Overall, as the number of rows scale up, the memory usage savings scale up as well in a very consistent way.

Most of these example benchmarks are bare minimum examples that only implement one or two features like pagination or sorting, but the kitchen-sink example is supposed to be our more realistic example that uses all of the features that TanStack Table has to offer.

One thing worth pointing out is that in the kitchen sink example case that only has 10 rows x 8 columns, Table V9 is actually using slightly more memory than Table V8. This is most likely because we've done some work to add even more internal memoization for CPU performance gains. But the increase in memory usage from those improvements is so low that this other refactor dwarfs it. We essentially unlocked a huge new budget for memory usage that we didn't have before, and that budget grows with the number of table-created objects. We can feel a bit more free to optimize for speed at the cost of a little more memory usage going forward.

If you're curious about how these benchmarks were run, you can view the repository here.

The benchmark runner uses Playwright and the Chrome DevTools Protocol. For each example, it:

  1. Builds the Vite example for production

  2. Starts vite preview

  3. Opens the page in a fresh Chromium context

  4. Waits for the table to report that it is ready

  5. Forces garbage collection with HeapProfiler.collectGarbage

  6. Records retained JS heap

  7. Records DOM counts and rendered row/cell counts

  8. Scrolls or paginates the table when the example has an interaction to measure

    This benchmark is designed to stress the thing that changed: the number of row, column, cell, and header objects that TanStack Table creates.

Let's get into how we achieved this performance improvement.

The big reveal: We used shared prototypes... and that's pretty much it. Let's go over the details.

What Table V8 Did

In TanStack Table V8, when the table, row, column, cell, header, etc. objects get created, both their values and methods were assigned directly to each object instance.

Simplified a bit, constructing a new row object looked like this:

That is a very natural way to write JavaScript. For normal application code, I wouldn't expect it to be written any other way. The issue is that in TanStack Table, this code might be inside of a loop that creates thousands or millions of rows. And each row has its own loop that creates perhaps dozens or hundreds of cells.

So you end up in a situation where there are potentially millions of object instances that all have their own copy of the same methods over and over again. Millions of nearly identical cell and row objects. Potentially dozens or hundreds of nearly identical column and header objects.

And the cost is not just the duplicate function objects. In this style, every arrow function can also carry a closure scope with it. That closure might capture the row, the table, caches, options, or other local values from the factory that created the object. Those closure scopes are retained per instance too, which is a big part of why the memory usage can scale so aggressively.

What Table V9 Does Instead

During the development of the TanStack Table V9 alpha, we introduced this refactor when creating every row, column, cell, and header object.

So instead of creating every row.getValue(), row.getUniqueValues(), etc. method for each row object potentially millions of times, we create them just once and then assign them to the row prototype, and then just assign the prototype to the new row object.

This also eliminates those per-instance method closures. The shared prototype method receives the specific row through this, so the row-specific state stays on the row object instead of being captured by a new function scope for every row.

We repeated this pattern for column, cell, and header objects too, though the largest impact was on the row objects, since those are the most likely to both scale and have a large amount of methods on them. We did not need to use prototype methods for the table object, because the table is already just 1 object instance.

Why Not Do This in Table V8?

There was an original PR from Michael Leibman that proposed this kind of refactor for TanStack Table V8. However, we discovered that this technically introduces subtle breaking changes. So we decided that this would be less risky to implement in Table V9 instead.

What are those subtle breaking changes? Mainly destructuring object methods doesn't work anymore.

Code like this breaks:

You have to use the method like this:

This worked in Table V8 because methods like getValue were arrow functions created inside the row factory. They closed over the row object, so they did not care how they were called.

In Table V9, the method is shared on the row prototype and uses its this context to know which row it is operating on. When you destructure a method like this, you get the function, but you lose the original receiver. Then this is undefined in strict mode, so the method cannot find the row. That strict-mode behavior applies automatically here because ES modules are always strict, and TanStack Table ships as modules.

The methods won't appear as own properties (e.g. in Object.keys(row), object spread, or JSON.stringify). They live on the prototype, though you'll still find them under [[Prototype]] in the console. This also means shallow clones like { ...row } will copy row data but drop the object's methods.

But you can still call the methods all the same. Calling row.getValue() still works because JavaScript will automatically look for the method on the prototype if it is not found on the object itself.

This is a tradeoff worth making in a breaking change release, especially if it is the only downside, but we couldn't push this change out into Table V8.

This is definitely a refactor worth making for a library like TanStack Table. I wonder how many other libraries or even applications out there could benefit from this same optimization when needing to scale. We definitely thought that it was worth sharing here in case it is useful for you.

If you are wondering what this means for you as a user of TanStack Table, hopefully this is just an invisible improvement that you'll barely notice. We will be documenting the small breaking changes in the migration guide for Table V8 to Table V9.