User-defined type guards
Teach the compiler what you already know.
A function returning arg is T (or asserts arg is T) hands the compiler proof it cannot derive on its own β so narrowing survives across calls, filters, and runtime validation.
The is predicate
A normal boolean function tells the compiler nothing about why it returned true. Give the return type the form x is Foo and a true result narrows x to Foo in the calling branch. The body still returns a plain boolean β the predicate is purely a signal to the type system, so it is your job to make the runtime check actually match the claim.
Filtering unions
Array.prototype.filter normally keeps the element type unchanged β filtering a (string | undefined)[] still yields (string | undefined)[]. Pass a predicate typed as x is string and the compiler narrows the result array to string[]. This is the idiomatic way to drop null/undefined without a cast.
Assertion functions
A function typed asserts x is T narrows by throwing instead of returning. After the call returns normally, x is T for the rest of the scope β no if needed. The bare form asserts x narrows away null/undefined/false. These power runtime validators where an invalid value should abort, not branch.
Soundness is on you
The compiler trusts your predicate blindly. If isString returns true for a number, every downstream narrowing is a lie and you get runtime errors with no compile warning. Keep the runtime check and the asserted type in lockstep.
Try it 5 examples
A basic isString predicate
TSintroOnly 'hello' enters the narrowed branch and prints HELLO; the 42 and null calls both fall through to (not a string). The predicate, not a cast, is what made value.toUpperCase() type-safe inside the if.