Prompt and context
A large TypeScript monorepo is moving from 5.9 to 6.0 and then evaluating the native-compiler path. The team observes that moving an unrelated declaration changes union ordering in emitted .d.ts files, and some inference errors appear or disappear. Explain why --stableTypeOrdering exists, why it is not a permanent optimization flag, and how to isolate real type defects without expanding upgrade risk.
The TypeScript 6.0 notes describe the option as a migration aid that makes ordering behavior match 7.0 more closely, while potentially slowing type checking by up to about 25%. It is not intended as a long-term default; when it reveals a difference, prefer explicit type arguments or annotations and keep a reproducible control build.
What the interviewer is testing
The interviewer wants to see whether you understand the relationship between declaration emit and type IDs, and whether you can separate ordering noise from real type errors. A strong answer covers project references, incremental caches, generated artifacts, performance baselines, CI rollback, and pinned compiler versions.
Questions to clarify first
- Did the change occur in
.d.tsoutput, editor display, or an actual assignability error? - Does the project use project references, incremental builds, or code generation?
- Are the 6.0 and target 7.0 compilers,
tsconfig, and dependencies locked? - What is the type-checking time budget, and which packages amplify the cost?
- Can a failed upgrade return to 5.9 or disable the diagnostic flag?
30-second answer
“I would treat --stableTypeOrdering as a 6-to-7 migration diagnostic, not a permanent performance switch. TypeScript 6.0’s type IDs depend on declaration-processing order, so an unrelated edit can change union output or expose fragile inference. I would compare locked builds with and without the flag across .d.ts output, errors, and duration; add explicit type arguments or annotations for real contracts; and limit the slower mode to affected projects. If performance or generators regress, I would keep a 5.9 rollback.”
Step-by-step deep dive
1. Pin the compiler and inputs
Lock TypeScript, Node, the package manager, tsconfig, dependencies, and generators. Run the same commit with 5.9, 6.0 defaults, 6.0 --stableTypeOrdering, and the target 7.0 toolchain. Save errors, declaration hashes, check duration, and cache-hit data.
2. Explain the source of ordering drift
The compiler assigns types order-sensitive IDs and uses them to order unions and declaration output. Adding an unrelated literal can change IDs, turning 100 | 500 into 500 | 100 in a .d.ts. The order alone is not a runtime change, but code that relied on fragile inference can observe a diagnostic change.
export function choose(flag: boolean) {
return flag ? 100 : 500;
}
// An unrelated declaration may change literal-union ordering in the declaration file.
const unrelated = 500;Do not treat declaration text order as semantic proof; inspect consumer assignability, type-argument inference, and API compatibility.
3. Use stableTypeOrdering correctly
The flag makes 6.0 ordering closer to 7.0 to expose cross-version differences. It can add up to about 25% to checking time, so enable it only on a migration branch, affected project, or diagnostic job. Record the scope instead of slowing the entire CI without an action.
4. Turn implicit inference into an explicit contract
If the flag exposes a call that depended on processing order, add explicit type arguments, variable annotations, public return types, or generic constraints. Recheck .d.ts, project references, and downstream consumers so the fix strengthens the contract rather than merely changing order.
5. Verify emit and incremental paths
Repeat builds with project references, declaration, generators, the language service, and incremental caches. Run once from a clean cache to distinguish ordering from cache contamination. Check generated artifacts for stability, but do not make formatted declaration text the only test oracle.
6. Set migration and rollback gates
Use a conditional build matrix to compare error counts, declaration APIs, check duration, and package diffs. Expand only when 6.0 and target 7.0 results are explainable, public APIs are unchanged, and performance stays within budget. On a serious regression, disable the flag, return to 5.9 or default ordering, and preserve commit-level evidence.
Model answer
I would lock the inputs and build a four-way matrix: 5.9, 6.0 default, 6.0 --stableTypeOrdering, and the target 7.0 toolchain. The option makes 6.0 ordering closer to 7.0 for migration diagnosis, but can slow checking and should not be global forever. I would compare .d.ts, real consumer errors, project references, generators, and cache behavior, then add explicit type arguments or annotations where inference depended on order. I would continue only after API, performance, and rollback gates pass.
Common mistakes
- Treating union-order changes as runtime changes → confuses declaration representation with execution → verify consumer types and API compatibility.
- Keeping
--stableTypeOrderingon forever → may add about 25% check time → scope it to migration diagnostics. - Comparing only editor display → misses declaration emit and downstream builds → audit
.d.ts, references, and packages. - Changing order just to make CI green → hides fragile inference → add an explicit contract and keep control results.
- No 5.9 rollback → upgrade failures become hard to isolate → pin versions and keep conditional builds.
Follow-up questions
Why can an unrelated declaration change union order?
TypeScript sorts using type IDs assigned during processing, so an unrelated declaration can change those IDs. The result is usually a declaration-output difference, but it can expose code that relied on inference order.
Should the flag always be enabled?
No. The documentation positions it as a 6.0-to-7.0 diagnostic aid that can significantly slow checking. Return to normal settings after migration diagnosis.
How do you distinguish a real error from ordering noise?
With locked inputs, compare errors, .d.ts, downstream assignability, and type contracts under default and stable ordering. Fix only a broken consumer contract or public API, not a harmless textual reorder.
Why prefer explicit type arguments?
They encode intent in source, remove an implicit dependency on processing order, and make 6.0, 7.0, and editors more likely to agree.
When can the migration diagnostic end?
When target-toolchain results are explainable, declaration APIs are stable, emit and incremental paths pass, performance fits the budget, and rollback evidence exists, disable the diagnostic flag and proceed with the upgrade.