TypeScript is a powerful superset of JavaScript that introduces static typing. It allows us to write safer, more predictable code. But just defining types isn't enough โ in real-world applications, we often deal with runtime data (user input, API responses, dynamic objects, etc.) that can bypass the compiler's safety net.
This blog post serves as your one-stop solution for all type checking techniques in TypeScript, covering basic to advanced concepts with organized sections and practical code examples.
๐งฑ 1. TypeScript Basics: Compile-Time Type Checking
โ Static Type Annotations
let age: number = 30;
let name: string = "Alice";
let isActive: boolean = true;
โ Arrays and Tuples
let scores: number[] = [90, 85, 70];
let user: [string, number] = ["Bob", 25]; // Tuple
โ Enums
enum Role {
Admin,
User,
Guest,
}
const userRole: Role = Role.User;
๐งพ 2. Using typeof for Primitive Runtime Checks
TypeScript types vanish at runtime, so we use JS operators like typeof to check types dynamically.
function logValue(value: unknown) {
if (typeof value === "string") {
console.log("It's a string:", value.toUpperCase());
} else if (typeof value === "number") {
console.log("It's a number:", value.toFixed(2));
} else {
console.log("Unknown type");
}
}
Supported typeof types:
"string""number""boolean""undefined""object""function""symbol""bigint"
๐ 3. Using instanceof for Class Checks
Use instanceof to check if an object is an instance of a class.
class Animal {
name: string = "Animal";
}
class Dog extends Animal {
breed: string = "Labrador";
}
const pet = new Dog();
if (pet instanceof Dog) {
console.log(pet.breed); // Labrador
}
๐ชช 4. Type Guards (Custom Runtime Type Checks)
โ Type Predicate Function
type User = { name: string; age: number };
type Admin = { name: string; role: string };
function isAdmin(user: User | Admin): user is Admin {
return (user as Admin).role !== undefined;
}
const u: User | Admin = { name: "Alex", role: "admin" };
if (isAdmin(u)) {
console.log("Admin role is:", u.role);
}
๐ฅ
user is Adminis a type predicate that narrows the type inside conditionals.
๐งฑ 5. Discriminated Unions
Very powerful way to model and type-check multiple variants.
type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; size: number };
type Shape = Circle | Square;
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
}
}
โ Discriminated unions help safely branch logic based on a common
kindproperty.
๐ 6. in Operator for Property Checks
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim();
} else {
animal.fly();
}
}
โ Use the
inoperator when there's no common discriminator (kindfield).
๐งฎ 7. Type Assertions (Be Careful!)
const someValue: unknown = "Hello World";
const strLength: number = (someValue as string).length;
โ ๏ธ Type assertions override the compiler. Use sparingly when youโre absolutely sure.
๐ง 8. Using typeof with Functions and Objects
const config = {
port: 3000,
host: "localhost",
};
type Config = typeof config; // Inferred type of object
function connect(cfg: Config) {
console.log(`Connecting to ${cfg.host}:${cfg.port}`);
}
๐ 9. Using keyof, Record, Partial, Pick, Omit
These are utility types to check and manipulate types.
type User = {
id: number;
name: string;
email: string;
};
type PartialUser = Partial<User>;
type PickName = Pick<User, "name">;
type WithoutEmail = Omit<User, "email">;
type UserRecord = Record<string, User>;
type UserKeys = keyof User; // "id" | "name" | "email"
๐ 10. Validating JSON or API Responses
โ Manual Runtime Check Example
type User = { id: number; name: string };
function isUser(data: any): data is User {
return (
typeof data === "object" &&
typeof data.id === "number" &&
typeof data.name === "string"
);
}
โ
With zod or io-ts (Schema Validation)
import { z } from "zod";
const UserSchema = z.object({
id: z.number(),
name: z.string(),
});
type User = z.infer<typeof UserSchema>;
const result = UserSchema.safeParse(JSON.parse(json));
if (result.success) {
console.log("Valid user:", result.data);
}
๐ฅ Use libraries like
zod,io-ts, oryupfor runtime validation in production.
๐งช 11. Advanced: Exhaustive Checking
Make sure you handle all variants in discriminated unions.
function handle(shape: Shape) {
switch (shape.kind) {
case "circle":
return shape.radius;
case "square":
return shape.size;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
โ This triggers a compile-time error if you miss a case.
๐ ๏ธ 12. Third-party Runtime Validators (Quick Overview)
| Library | Usage | Notes |
|---|---|---|
zod |
Runtime validation + TypeScript | Modern and typesafe |
io-ts |
Powerful validation + FP | Functional style |
yup |
Schema-based validation | JS-friendly |
class-validator |
Decorator-based validation | Works with classes |
๐งช 13. unknown vs any
| Type | Description |
|---|---|
any |
Opt-out of type checking (dangerous) |
unknown |
Safer, forces type narrowing or checks |
function process(input: unknown) {
if (typeof input === "string") {
console.log(input.toUpperCase());
}
}
๐ช 14. Type Narrowing in Complex Conditions
function example(value: string | string[] | null) {
if (value && typeof value === "string") {
console.log("It's a string:", value);
} else if (Array.isArray(value)) {
console.log("Array:", value.join(", "));
}
}
๐ฅ Bonus: Type-Safe API Layer
type ApiResponse<T> = {
status: number;
data: T;
error?: string;
};
async function fetchUser(): Promise<ApiResponse<User>> {
const res = await fetch("/api/user");
const json = await res.json();
if (!isUser(json)) {
return { status: 500, data: null as any, error: "Invalid data" };
}
return { status: 200, data: json };
}
๐ฆ Tools & Plugins for Type Safety
- ESLint + TypeScript rules
- tsconfig strict mode
-
ts-nodefor dev runtime -
typescript-json-schemafor schema generation
โ Final Checklist for Type Safety
| Task | Technique |
|---|---|
| Compile-time safety | Type annotations, interfaces, types |
| Runtime safety | Type guards, typeof, instanceof
|
| Object validation | Schema validators (zod, io-ts, etc.) |
| Exhaustive checks | Discriminated unions + never
|
| API input/output typing | Generic wrappers, type-safe contracts |
๐ง Conclusion
TypeScript gives you the tools, but itโs up to you to use them wisely.
Think of type checking as both a compile-time shield and a runtime armor. ๐ก๏ธ
With all these strategies and techniques, you're now ready to build robust, type-safe applications that prevent bugs and improve maintainability.
Top comments (0)