Skip to main content

Type Guards and Advanced Types

Level: Beginner

ℹ️ What You'll Learn
  • Type guards with typeof and instanceof
  • Discriminated unions for safe pattern matching
  • User-defined type predicates (is keyword)
  • Type narrowing in if/else branches
  • Switch statements with exhaustive checks
  • Asserts for narrowing assumptions
  • Conditional types for advanced patterns
  • SMS status and role type checking

Why This Matters

Type Guards and Advanced Types helps you write safer frontend code. In ASP.NET Core projects, TypeScript makes API response shapes, form models, DOM values, and reusable helpers easier to maintain.

Narrow types safely and handle complex scenarios.

The Problem

JavaScript allows many mistakes to appear only at runtime. This lesson shows how Type Guards and Advanced Types lets TypeScript catch wrong values, missing properties, and unsafe assumptions before the page reaches users.

Type Checking

function processValue(value: string | number): void {
if (typeof value === 'string') {
// Inside: value is string
console.log(value.toUpperCase());
} else {
// Inside: value is number
console.log(value.toFixed(2));
}
}

Instance Checking

class Student {
name: string;
constructor(name: string) {
this.name = name;
}
}

function printName(obj: any): void {
if (obj instanceof Student) {
// Now: obj is Student
console.log(obj.name);
} else {
// obj is something else
console.log('Not a student');
}
}

Discriminated Unions

type Result<T> =
| { success: true; data: T }
| { success: false; error: string };

function handleResult<T>(result: Result<T>): void {
if (result.success) {
// Inside: success is true, data exists
console.log(result.data);
} else {
// Inside: success is false, error exists
console.error(result.error);
}
}

const studentResult: Result<string> = {
success: true,
data: "Ravi Kumar"
};

handleResult(studentResult);

Type Predicates

function isStudent(obj: any): obj is Student {
return obj && typeof obj.name === 'string' && typeof obj.id === 'number';
}

function process(obj: any): void {
if (isStudent(obj)) {
// Now TypeScript knows obj is Student
console.log(obj.name, obj.id);
}
}

Optional Chaining and Nullish Coalescing

interface Student {
id: number;
name: string;
address?: { city: string };
}

const student: Student = { id: 101, name: "Ravi" };

// Optional chaining - safe null access
const city = student.address?.city; // undefined if address doesn't exist

// Nullish coalescing - default if null/undefined
const displayCity = student.address?.city ?? 'Unknown City';

Generic Constraints with Type Guards

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] | string {
const value = obj[key];

if (value === null || value === undefined) {
return 'Not set';
}

return value;
}

interface Student {
name: string;
email?: string;
}

const student: Student = { name: "Ravi" };
const value = getProperty(student, 'email'); // string | undefined

Real API Response Types

// Different response types based on status
type ApiResponse<T> =
| { status: 'success'; data: T }
| { status: 'error'; code: number; message: string }
| { status: 'loading' };

async function fetchStudent(id: number): Promise<ApiResponse<Student>> {
try {
const response = await fetch(`/api/students/${id}`);
if (!response.ok) {
return {
status: 'error',
code: response.status,
message: response.statusText
};
}

const data = await response.json();
return {
status: 'success',
data
};
} catch (error) {
return {
status: 'error',
code: 0,
message: error instanceof Error ? error.message : 'Unknown error'
};
}
}

// Usage
const result = await fetchStudent(101);

if (result.status === 'success') {
console.log(result.data.name); // TypeScript knows data exists
} else if (result.status === 'error') {
console.error(result.message); // TypeScript knows message exists
}

SMS Example

interface StudentSuccess {
type: 'student';
id: number;
name: string;
className: string;
}

interface ExamSuccess {
type: 'exam';
examId: number;
studentId: number;
marks: number;
}

type ApiData = StudentSuccess | ExamSuccess;

function displayData(data: ApiData): void {
switch (data.type) {
case 'student':
console.log(`Student: ${data.name} in class ${data.className}`);
break;
case 'exam':
console.log(`Exam: Student ${data.studentId} got ${data.marks}`);
break;
}
}

// Safely handle different types
const studentData: StudentSuccess = {
type: 'student',
id: 101,
name: 'Ravi Kumar',
className: '10'
};

displayData(studentData);

Key Takeaways

  • Type guards narrow types safely
  • Discriminated unions for complex data
  • Optional chaining for null safety
  • Type predicates for custom checks
  • Real API responses use these patterns
  • Next: Complete SMS example
💡 Backend Developer Tip

Discriminated unions are powerful for API responses with different success/error formats. Always discriminate on a literal type.

⚠️ Type Guard Mistakes
  1. Not using type guards — Access wrong properties
  2. Wrong discriminator — Union doesn't narrow
  3. Forgetting null checks — Optional chaining not used
  4. Overly broad unions — Hard to narrow down
🤖Use AI to Learn Faster

Use ChatGPT, Claude, or Copilot to go deeper on Type Guards. Try these prompts:

  • "When use discriminated unions?"
  • "Why use optional chaining?"
  • "How do type predicates work?"
  • "Quiz me on type guards"

💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.

Quick Definitions

  • Type Guards and Advanced Types - The main TypeScript concept explained in this lesson.
  • Type - A rule that describes what kind of value is allowed.
  • Interface - A contract that describes the shape of an object.
  • DTO model - The frontend shape of data coming from ASP.NET Core Web API.

Common Mistakes

  • Using any instead of defining a useful type
  • Making types too complex before understanding the data
  • Forgetting that API data still needs runtime validation
  • Confusing JavaScript runtime behavior with TypeScript compile-time checks
  • Not reusing shared types for repeated API models

Practice Task

Create a small TypeScript example using Type Guards and Advanced Types. Keep it connected to a School Management System scenario.

Suggested practice:

  1. Define a Student, Teacher, Attendance, or Marks model.
  2. Write one function or class using that type.
  3. Add one intentionally wrong value and read the TypeScript error.
  4. Fix the type or the data shape.
  5. Explain the type contract in your own words.

Quick Revision

QuestionAnswer
What is the main idea?Use TypeScript to make Type Guards and Advanced Types safer and clearer.
Where is it used?API models, forms, DOM code, helpers, and React/Next.js apps.
What should beginners avoid?Overusing any and ignoring API validation.
What is the best debugging habit?Read the TypeScript error and compare it with the expected data shape.
🎯 How would you explain Type Guards and Advanced Types in an interview?

Type Guards and Advanced Types is a TypeScript concept that improves JavaScript safety by making data shapes and function expectations clear. I would explain the problem it solves, show a small example, and mention how it helps with API-connected frontend code.

🎯 Where is this used in real projects?

It is used in API DTOs, form models, DOM access, helper functions, React props, service classes, and reusable frontend utilities.

Next Article

Complete SMS Application in TypeScript ->

nexcoding.in