Auto-generated tile from GitHub (10 skills)
92
94%
Does it follow best practices?
Impact
92%
1.16xAverage score across 44 eval scenarios
Advisory
Suggest reviewing before use
Type narrowing is TypeScript's ability to refine types based on control flow analysis. When you check a type condition, TypeScript narrows the type within that code block.
typeof Guardsfunction processValue(value: string | number) {
if (typeof value === "string") {
// value is string here
return value.toUpperCase();
}
// value is number here
return value.toFixed(2);
}instanceof Guardsfunction logError(error: Error | string) {
if (error instanceof Error) {
// error is Error here
console.log(error.stack);
} else {
// error is string here
console.log(error);
}
}function printName(name: string | null | undefined) {
if (name) {
// name is string here (truthy)
console.log(name.toUpperCase());
}
}function example(x: string | number, y: string | boolean) {
if (x === y) {
// Both are string here (only common type)
console.log(x.toUpperCase());
console.log(y.toUpperCase());
}
}in Operatorinterface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function move(animal: Fish | Bird) {
if ("swim" in animal) {
// animal is Fish here
animal.swim();
} else {
// animal is Bird here
animal.fly();
}
}Use a common property to discriminate between types:
interface Circle {
kind: "circle";
radius: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Triangle {
kind: "triangle";
base: number;
height: number;
}
type Shape = Circle | Rectangle | Triangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
// shape is Circle here
return Math.PI * shape.radius ** 2;
case "rectangle":
// shape is Rectangle here
return shape.width * shape.height;
case "triangle":
// shape is Triangle here
return (shape.base * shape.height) / 2;
}
}Use never to ensure all cases are handled:
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
default:
// If a new shape is added, this will error
const _exhaustiveCheck: never = shape;
throw new Error(`Unhandled shape: ${_exhaustiveCheck}`);
}
}Functions that return value is Type:
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird) {
if (isFish(pet)) {
// pet is Fish here
pet.swim();
} else {
// pet is Bird here
pet.fly();
}
}function isNotNull<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
const values = [1, null, 2, undefined, 3];
const filtered = values.filter(isNotNull);
// filtered is number[]function hasProperty<T extends object, K extends string>(
obj: T,
key: K
): obj is T & Record<K, unknown> {
return key in obj;
}
const data: unknown = { name: "Alice" };
if (typeof data === "object" && data !== null && hasProperty(data, "name")) {
// data.name is now accessible
console.log(data.name);
}Functions that throw on invalid input:
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error(`Expected string, got ${typeof value}`);
}
}
function processInput(input: unknown) {
assertIsString(input);
// input is string here
console.log(input.toUpperCase());
}interface User {
id: string;
name: string;
}
function assertIsUser(value: unknown): asserts value is User {
if (
typeof value !== "object" ||
value === null ||
!("id" in value) ||
!("name" in value)
) {
throw new Error("Invalid user object");
}
}
function handleData(data: unknown) {
assertIsUser(data);
// data is User here
console.log(data.name);
}Must use function declaration, not arrow functions:
// Error: Assertions require every name in the call target to be
// declared with an explicit type annotation.
const assertString = (value: unknown): asserts value is string => {
if (typeof value !== "string") throw new Error("Not a string");
};
// Correct
function assertString(value: unknown): asserts value is string {
if (typeof value !== "string") throw new Error("Not a string");
}Combine type predicates with opaque types for validated data:
type ValidEmail = string & { __brand: "ValidEmail" };
function isValidEmail(email: string): email is ValidEmail {
return email.includes("@") && email.includes(".");
}
function sendEmail(email: ValidEmail) {
// We know email has been validated
}
function handleSubmit(email: string) {
if (!isValidEmail(email)) {
throw new Error("Invalid email");
}
// email is ValidEmail here
sendEmail(email);
}type Item = { type: "a"; value: string } | { type: "b"; count: number };
const items: Item[] = [
{ type: "a", value: "hello" },
{ type: "b", count: 42 },
];
// Filter to specific type
const typeAItems = items.filter(
(item): item is { type: "a"; value: string } => item.type === "a"
);
// typeAItems is { type: "a"; value: string }[]TypeScript can't always track narrowing across function calls:
function isString(x: unknown): x is string {
return typeof x === "string";
}
function example(value: string | number) {
const isStr = isString(value);
if (isStr) {
// value is still string | number here!
// TypeScript doesn't narrow based on boolean variables
}
// Must check inline
if (isString(value)) {
// value is string here
}
}interface SuccessResponse<T> {
status: "success";
data: T;
}
interface ErrorResponse {
status: "error";
error: {
code: string;
message: string;
};
}
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
function isSuccess<T>(response: ApiResponse<T>): response is SuccessResponse<T> {
return response.status === "success";
}
async function fetchUser(): Promise<ApiResponse<User>> {
// ...
}
async function handleUser() {
const response = await fetchUser();
if (isSuccess(response)) {
// response.data is User
console.log(response.data.name);
} else {
// response.error is accessible
console.error(response.error.message);
}
}| Technique | Use Case |
|---|---|
typeof | Primitive type checks |
instanceof | Class instance checks |
in operator | Property existence checks |
| Discriminated unions | Multiple related types with common discriminant |
| Type predicates | Custom narrowing logic |
| Assertion functions | Validation with early error throwing |
function example(value: string | null) {
if (value !== null) {
// value is string here
setTimeout(() => {
// value is string | null again!
// TypeScript is conservative about callbacks
}, 0);
}
}// Wrong - doesn't narrow
function isFish(pet: Fish | Bird) {
return "swim" in pet; // Just returns boolean
}
// Correct - narrows the type
function isFish(pet: Fish | Bird): pet is Fish {
return "swim" in pet;
}function example(value: { a?: string; b?: number }) {
// This doesn't narrow as expected
if (value.a || value.b) {
// Neither a nor b is guaranteed to exist
}
// Use specific checks
if (value.a !== undefined) {
// value.a is string here
}
}evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
skills
documentation
fastify
init
linting-neostandard-eslint9
node
nodejs-core
rules
oauth
octocat
snipgrapher