blog: Table v9 type perf blog#979
Conversation
📝 WalkthroughWalkthroughThis PR adds a new blog post documenting the TypeScript type-checking performance improvements made to TanStack Table V9 from alpha.54 through beta.11, detailing the shift from conditional unions to feature maps, optimization techniques, measurement methodology, and practical guidance for developers. ChangesTypeScript Performance Optimization Blog Post
🎯 1 (Trivial) | ⏱️ ~3 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/blog/tanstack-table-v9-typescript-performance.md`:
- Line 16: Fix the typo in the intro sentence: replace the phrase "turned our to
be one of our bigger optimizations" with "turned out to be one of our bigger
optimizations" so the sentence reads correctly.
- Around line 257-261: The paragraph overstates the safety of the `in out`
variance claim; replace the sentences that say "`in out` annotation is simply
trusted" and "sound to do regardless of the structure" with a more accurate
explanation: state that `in out` asserts invariance for the compiler's
instantiation-based comparisons and can bypass structural variance probing
(i.e., the compiler will use the annotated invariance rather than derive
variance from the type's structure), that this usually only restricts
assignability but can also remove relations the code relies on (as shown by
`TValue` breaking the build), and therefore it should be applied only where
parameters are invariant in practice (keeping the `Table_Internal` and `TValue`
examples as-is to illustrate the benefit and caveat).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0e6d1f5f-d5d0-48de-88f5-ab374867343d
⛔ Files ignored due to path filters (1)
public/blog-assets/tanstack-table-v9-typescript-performance/header.pngis excluded by!**/*.png
📒 Files selected for processing (1)
src/blog/tanstack-table-v9-typescript-performance.md
|
|
||
| If you had been using the Table V9 alphas, there's a chance that you could feel a bit of slowness in your editor. Good news, though! Between the alpha and the latest beta, we cut TypeScript's type-checking work by 66-85% across every package and example! The latest beta now type-checks faster than our alpha versions from last week by a wide margin, and the editor experience is back to feeling nearly instant. | ||
|
|
||
| This post covers where the cost came from, how we measured it, and the specific changes that fixed these issues. One of those changes is a still overlooked TypeScript feature that many library authors still seem to barely use, and it turned our to be one of our bigger optimizations, turning a trade-off into a win across the board. |
There was a problem hiding this comment.
Fix the typo in the introduction.
“turned our” should be “turned out.” Small, but this is front-matter copy and it’s immediately visible.
🧰 Tools
🪛 LanguageTool
[style] ~16-~16: Consider using a different verb for a more formal wording.
Context: ...sured it, and the specific changes that fixed these issues. One of those changes is a...
(FIX_RESOLVE)
[grammar] ~16-~16: Use a hyphen to join words.
Context: ... issues. One of those changes is a still overlooked TypeScript feature that many ...
(QB_NEW_EN_HYPHEN)
[grammar] ~16-~16: Ensure spelling is correct
Context: ...still seem to barely use, and it turned our to be one of our bigger optimizations, ...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/blog/tanstack-table-v9-typescript-performance.md` at line 16, Fix the
typo in the intro sentence: replace the phrase "turned our to be one of our
bigger optimizations" with "turned out to be one of our bigger optimizations" so
the sentence reads correctly.
| `in out` declares the parameter invariant, and it is not a cache. It changes no behavior here, because these parameters already appear in both input and output positions throughout, so they were invariant in practice anyway. One subtlety worth being precise about: unlike a lone `in` or `out`, which the compiler does check against the type's structure, an `in out` annotation is simply trusted. That is sound to do regardless of the structure, because invariance is the strictest relation there is. Asserting it can only remove assignments the compiler would otherwise have allowed, never permit an unsound one. What the annotation buys is the shortcut. The compiler relates instantiations by their type arguments directly, with no variance probing and no structural fallback. | ||
|
|
||
| Annotating `Table_Internal` alone took the react adapter from 136k down to 66k, below where it started. Annotating the rest of table-core's generic interfaces (166 parameters in total) took it to 54.7k, shaved another 3% off the core, and moved the kitchen-sink example from flat to 13% better. What had looked like a core-versus-adapters trade-off became a win everywhere, for the price of two keywords per type parameter. | ||
|
|
||
| One caveat from the same experiment, because variance annotations are not free everywhere. We tried annotating `TValue`, the cell value parameter, and the build broke immediately. `TValue` is genuinely covariant in output-position types, and forcing it invariant rejected the `Column<TF, TD, string>` to `Column<TF, TD, unknown>` widening that both the library and your apps rely on. The annotation itself was accepted, exactly as the "invariance is always safe" rule predicts. What it removed was a relation we actually needed, and the build caught it. So the rule of thumb is to only annotate parameters that are invariant in practice, and to treat a failing build as the check the annotation does not do for you. Measure, don't assume. |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the surrounding text to ensure the exact claim/wording is present.
sed -n '230,290p' src/blog/tanstack-table-v9-typescript-performance.md | nl -baRepository: TanStack/tanstack.com
Length of output: 108
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="src/blog/tanstack-table-v9-typescript-performance.md"
# Print the referenced region with line numbers (avoid nl).
awk -v start=245 -v end=275 'NR>=start && NR<=end {printf "%d\t%s\n", NR, $0}' "$file"Repository: TanStack/tanstack.com
Length of output: 4065
🌐 Web query:
TypeScript handbook variance annotations in out generics instantiation-based comparisons structural variance trusted
💡 Result:
TypeScript utilizes a structural type system, meaning that type compatibility—and consequently variance—is naturally emergent from the members of a type rather than its name or explicit heritage [1][2][3]. Because of this, variance is automatically inferred by the compiler [1][4]. Variance annotations (in, out, and in out) are optional modifiers that allow developers to explicitly declare the variance of a generic type parameter [1][5]. Their primary purpose is not to change the structural behavior of a type, but to serve as a tool for enforcing intent, assisting with complex or recursive types, and potentially improving type-checking performance [1][4][6]. Key considerations for variance annotations include: 1. Structural vs. Instantiation-Based Comparisons: TypeScript performs two types of comparisons: structural and instantiation-based [1][4]. Variance annotations are only effective during instantiation-based comparisons [1][4]. Whether TypeScript chooses one over the other is internal, unspecified, and subject to change across compiler versions [1][4]. Therefore, you should only add a variance annotation if it matches the type's structural definition [1][4]. 2. Risk of Mismatch: Annotations do not override structural rules [1][4]. If an annotation is inconsistent with the actual structural behavior of the type, you will encounter unpredictable behavior or inconsistencies in type checking [1][4][5]. You should not use these annotations to "force" a specific variance [1]. 3. Inference Preference: Because TypeScript's automatic inference is highly accurate and handles the vast majority of cases, the official handbook strongly discourages using variance annotations in normal code [1][4]. They are intended for rare, specific scenarios where inference might fail, such as certain circular type definitions, or to provide explicit documentation of intent [1][4][6]. In summary, trust the compiler's automatic inference as the primary source of truth for variance. Use explicit annotations only when you have a specific, identified need and are certain the annotation matches the structural reality of the generic type [1][4].
Citations:
- 1: https://www.typescriptlang.org/docs/handbook/2/generics
- 2: https://www.typescriptlang.org/docs/handbook/type-compatibility.html
- 3: https://github.com/microsoft/TypeScript-wiki/blob/a425a2d1b26e81d9989b0f51499591181f93e956/FAQ.md
- 4: https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/handbook-v2/Type%20Manipulation/Generics.md
- 5: Optional variance annotations microsoft/TypeScript#48240
- 6: https://www.typescript-training.com/course/intermediate-v2/11-covariance-contravariance/
🌐 Web query:
TypeScript handbook generics variance annotations in out trusted simply trusted structural variance instantiation-based comparisons quotes
💡 Result:
In TypeScript, variance is a naturally emergent property of its structural type system [1][2]. TypeScript automatically infers the variance of generic type parameters based on how they are used within a type [1][3]. Because of this, explicit variance annotations—introduced in TypeScript 4.7 using in and out keywords—are almost never necessary and should generally be avoided in normal code [1][2][4]. Key points regarding variance annotations and structural behavior: 1. Automatic Inference: TypeScript calculates variance by observing how a type parameter is used (e.g., in input vs. output positions) [3][4]. You should only use annotations if you have identified a specific need, such as debugging or improving performance in complex, recursive type definitions [1][2][5][6]. 2. Structural vs. Instantiation-based Comparisons: Variance annotations are only consulted during instantiation-based comparisons [1][2][7]. They do not change the structural behavior of a type [1][2]. Because TypeScript's use of structural versus instantiation-based comparisons is not a specified behavior and can change between versions, you must only write variance annotations that match the actual structural variance of the type [1][2][7]. 3. Trust and Usage: You should not use variance annotations to "force" or change the variance of a type to something it is not structurally [1][7]. Doing so does not modify the type's behavior in structural contexts and can lead to surprising or incorrect type-checking results [1][2][7]. If you choose to use them, ensure the annotation is the same as the structurally measured variance [1][7]. 4. Annotation Meanings: - out T: Declares the type parameter as covariant (T only appears in output positions) [3][4][8]. - in T: Declares the type parameter as contravariant (T only appears in input positions) [3][4][8]. - in out T: Declares the type parameter as invariant [3][4][8]. In summary, variance annotations are an advanced tool meant to match—not override—the structural reality of your types [1][7]. They should be used sparingly and removed once the specific need (such as debugging) is resolved [1][2].
Citations:
- 1: https://www.typescriptlang.org/docs/handbook/2/generics
- 2: https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/handbook-v2/Type%20Manipulation/Generics.md
- 3: Optional variance annotations microsoft/TypeScript#48240
- 4: https://docs.syntblaze.com/typescript/generics/variance-annotation
- 5: https://syntaxstudy.com/typescript/advanced-types/ts-advanced-types-2
- 6: https://www.typescript-training.com/course/intermediate-v2/11-covariance-contravariance/
- 7: Variance Annotations do not impact "infer" correctly microsoft/TypeScript#55920
- 8: https://oneuptime.com/blog/post/2026-01-30-typescript-variance-annotations/view
Tone down the “variance annotation is simply trusted / always safe regardless of structure” claim for in out.
TypeScript variance annotations are consulted for instantiation-based comparisons and are intended to match the type’s structural variance; the current paragraph overstates safety (“simply trusted”, “sound regardless of structure”, “never permit an unsound one”).
Suggested rewording
- One subtlety worth being precise about: unlike a lone `in` or `out`, which the compiler does check against the type's structure, an `in out` annotation is simply trusted. That is sound to do regardless of the structure, because invariance is the strictest relation there is. Asserting it can only remove assignments the compiler would otherwise have allowed, never permit an unsound one. What the annotation buys is the shortcut. The compiler relates instantiations by their type arguments directly, with no variance probing and no structural fallback.
+ One subtlety worth being precise about: variance annotations are consulted for instantiation-based comparisons, and `in out` pins the parameter as invariant for those comparisons—letting the compiler skip variance probing/structural fallback when relating instantiations. Like any variance annotation, it’s meant to reflect the type’s structural variance; if it doesn’t, assignability can change (as your `TValue` break shows).🧰 Tools
🪛 LanguageTool
[uncategorized] ~259-~259: “React” is a proper noun and needs to be capitalized.
Context: ...otating Table_Internal alone took the react adapter from 136k down to 66k, below wh...
(A_GOOGLE)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/blog/tanstack-table-v9-typescript-performance.md` around lines 257 - 261,
The paragraph overstates the safety of the `in out` variance claim; replace the
sentences that say "`in out` annotation is simply trusted" and "sound to do
regardless of the structure" with a more accurate explanation: state that `in
out` asserts invariance for the compiler's instantiation-based comparisons and
can bypass structural variance probing (i.e., the compiler will use the
annotated invariance rather than derive variance from the type's structure),
that this usually only restricts assignability but can also remove relations the
code relies on (as shown by `TValue` breaking the build), and therefore it
should be applied only where parameters are invariant in practice (keeping the
`Table_Internal` and `TValue` examples as-is to illustrate the benefit and
caveat).
| --- | ||
| title: TypeScript Performance in TanStack Table V9 | ||
| published: 2026-06-13 | ||
| excerpt: TanStack Table V9's types do a lot more than V8's did. Here's how we cut type instantiations by 66-85% across every package between the alpha.54 and beta.11 to keep the editor experience feeling nearly instant. |
There was a problem hiding this comment.
"nearly"? why so defensive?
There was a problem hiding this comment.
In my own experience, there's usually still a noticeable few tenths of a second for the type to pop up. Plus there is still a lot of things going on that make this a slower typescript experience compared to v8. It's just way less of a time cost now and the tradeoffs are worth it.
So I didn't think "nearly" was defensive, just accurate
|
|
||
|  | ||
|
|
||
| TanStack Table V9 has a much more capable, though more complex, type system than V8. The types in Table may not be as complicated as a project like TanStack Router or Form, but it has still grown more complex in V9 than it ever had been in previous versions. |
There was a problem hiding this comment.
is "type system" the right term here?
There was a problem hiding this comment.
I want a better term. What should it be?
There was a problem hiding this comment.
"type-level API" maybe?
|
|
||
| TanStack Table V9 has a much more capable, though more complex, type system than V8. The types in Table may not be as complicated as a project like TanStack Router or Form, but it has still grown more complex in V9 than it ever had been in previous versions. | ||
|
|
||
| If you had been using the Table V9 alphas, there's a chance that you could feel a bit of slowness in your editor. Good news, though! Between the alpha and the latest beta, we cut TypeScript's type-checking work by 66-85% across every package and example! The latest beta now type-checks faster than our alpha versions from last week by a wide margin, and the editor experience is back to feeling nearly instant. |
|
|
||
| If you had been using the Table V9 alphas, there's a chance that you could feel a bit of slowness in your editor. Good news, though! Between the alpha and the latest beta, we cut TypeScript's type-checking work by 66-85% across every package and example! The latest beta now type-checks faster than our alpha versions from last week by a wide margin, and the editor experience is back to feeling nearly instant. | ||
|
|
||
| This post covers where the cost came from, how we measured it, and the specific changes that fixed these issues. One of those changes is a still overlooked TypeScript feature that many library authors still seem to barely use, and it turned our to be one of our bigger optimizations, turning a trade-off into a win across the board. |
Summary by CodeRabbit