DEV Community

Dev Cookies
Dev Cookies

Posted on

๐Ÿ›ก๏ธ One-Stop Guide to Type Safety in TypeScript: All Ways to Perform Type Checks

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;
Enter fullscreen mode Exit fullscreen mode

โœ… Arrays and Tuples

let scores: number[] = [90, 85, 70];
let user: [string, number] = ["Bob", 25]; // Tuple
Enter fullscreen mode Exit fullscreen mode

โœ… Enums

enum Role {
  Admin,
  User,
  Guest,
}

const userRole: Role = Role.User;
Enter fullscreen mode Exit fullscreen mode

๐Ÿงพ 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");
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿชช 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);
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ฅ user is Admin is 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;
  }
}
Enter fullscreen mode Exit fullscreen mode

โœ… Discriminated unions help safely branch logic based on a common kind property.


๐Ÿ“š 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();
  }
}
Enter fullscreen mode Exit fullscreen mode

โœ… Use the in operator when there's no common discriminator (kind field).


๐Ÿงฎ 7. Type Assertions (Be Careful!)

const someValue: unknown = "Hello World";

const strLength: number = (someValue as string).length;
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ 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}`);
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ 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"
Enter fullscreen mode Exit fullscreen mode

๐Ÿ” 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"
  );
}
Enter fullscreen mode Exit fullscreen mode

โœ… 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);
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ฅ Use libraries like zod, io-ts, or yup for 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;
  }
}
Enter fullscreen mode Exit fullscreen mode

โœ… 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());
  }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿช– 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(", "));
  }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ฅ 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 };
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ฆ Tools & Plugins for Type Safety

  • ESLint + TypeScript rules
  • tsconfig strict mode
  • ts-node for dev runtime
  • typescript-json-schema for 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)