The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
To preserve narrowing for a child node, check the child itself—preferably through TypeScript’s built-in ts.isX guard—and keep the checked value stable while you use it. A guard on the parent does not, by itself, make every child a more specific type.
Why a parent check does not narrow its child
TypeScript narrows values according to the checks that apply to those values. If you establish that node is a call expression, the compiler can let you access its expression child. But the parent check does not prove that the child is an identifier, property access, or any other particular node type. Check the child separately before using properties specific to that type.
The TypeScript Handbook describes a user-defined type guard as a function with a return type written as a type predicate: parameterName is Type. That signature promises callers that when the function returns true, the supplied value is of the named type. Its runtime implementation must actually establish that promise. See the TypeScript Handbook’s narrowing guide.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Use a built-in guard and a stable child value
The Compiler API provides many node guards, including ts.isCallExpression and ts.isIdentifier. When a matching built-in guard exists, use it rather than recreating the syntax-kind test. A reusable wrapper can give a project a named predicate while delegating the actual runtime check to TypeScript:
#1 Best Overall
import * as ts from "typescript";
function isIdentifier(node: ts.Node): node is ts.Identifier {
return ts.isIdentifier(node);
}
function inspect(node: ts.Node): void {
if (ts.isCallExpression(node)) {
const expression = node.expression;
if (isIdentifier(expression)) {
// expression is narrowed to ts.Identifier here.
console.log(expression.text);
}
}
}
The outer check establishes that the parent is a call expression, making its expression child available. The local constant gives the guard and subsequent use the same value to track. Inside the inner branch, expression is narrowed to ts.Identifier, so its text property is available.
When to check the property directly
For a simple expression, you can check a child property directly in the condition and use it within that branch. A local constant is often easier to read when the child is used more than once: it makes clear which value was checked and reduces the chance that a later access refers to a different expression.
Rank #2
- TypeScript implements a superset of syntax for strictly typed development, facilitating deep static analysis and enhanced development environment integration. The compiler translates source into standard script formats, ensuring parity across any runtime.
- TypeScript is ideal for front-end developers, full-stack engineers, and software architects who build large-scale web applications. It serves those looking to improve code excellence, reduce bugs through static checking, and maintain complex projects more.
- Lightweight, Classic fit, Double-needle sleeve and bottom hem
Make custom predicates prove what they promise
A concrete predicate such as isIdentifier(node: ts.Node): node is ts.Identifier is straightforward to understand and consume. Prefer a wrapper around the corresponding ts.isX guard where one exists. If you write a custom runtime test, ensure its true case really proves the entire type in the predicate signature; a test that checks only one convenient property may not justify the promise.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11A SyntaxKind comparison can be appropriate when it accurately identifies the claimed node kind, but do not use a type assertion or an incomplete test to make the compiler accept a broader claim. Generic wrappers can reduce repetition, but their predicate inference and soundness depend on their precise types and the TypeScript version in the project. There is no single generic signature established here as sound and stable across all Compiler API releases, so validate such a helper against the installed version rather than assuming a pattern will carry over unchanged.
Keep traversal separate from narrowing
The Compiler API treats Node as the basic AST building block. For recursive syntax traversal, ts.forEachChild visits a node’s children; a type guard answers whether a particular value is a particular node type. Microsoft’s Compiler API guide demonstrates visiting nodes and using built-in predicates such as ts.isClassDeclaration(node) before accessing declaration-specific properties.
Use a child-specific guard at the point you need a narrower child type. Traversing syntax alone does not require a type checker; reach for checker APIs when you need semantic information such as symbols or resolved types.
Why a narrowing can disappear
TypeScript’s control-flow analysis is sensitive to reachability and changes in the value being checked. A fact established by a guard is useful only while the compiler can still relate that fact to the value you use. Reassignment, mutation, or checking one expression and later reading another can break that connection.
The TypeScript 4.4 release notes document cases where narrowing can survive an extracted condition: the guarded value is a const, a readonly property, or an unmodified parameter. This is not a blanket guarantee for mutable properties or every closure. Keep the checked child stable between its guard and use, and verify the exact behavior with the compiler version and code pattern in your project. Read the TypeScript 4.4 release notes for the documented cases.
Best Value
Check the Compiler API version before copying examples
Compiler API examples are version-sensitive. The Microsoft wiki page for Using the Compiler API, crawled in October 2026, warns that its contents describe TypeScript 6.0 and earlier and that TypeScript 7.1 will have a different API. Check the TypeScript version installed in your project before adopting examples, especially when moving across that transition. The ts.isX pattern shown here illustrates how to delegate a predicate to a built-in guard; it is not a promise that every API detail is unchanged in every release.
What the traversal performance figures do—and do not—say
In its TypeScript 4.9 release notes, Microsoft reported that refactoring forEachChild yielded up to a 20% reduction in time spent in the binding phase and across language-service operations, and up to a 3% reduction in time spent generating project output. Those figures describe the TypeScript team’s compiler work, not benchmarks of custom AST traversal or user-defined type guards. They are not evidence that wrapping a built-in guard improves performance. See the TypeScript 4.9 release notes.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.

