While developing a TypeScript-based web application, you created a feature that fetches user data from an API. Sometimes, the API is down or returns an error. To handle such cases without crashing the app, you used a try-catch block inside an async function.
This way, if the API call fails, you can display an error message, such as “Something went wrong, please try again later,” instead of a blank screen or a broken app.
In this article, I’ll cover several effective ways of exception handling in TypeScript (including basic try/catch, custom error types, and async error handling). So let’s dive in!
What is Exception Handling in TypeScript?
TypeScript, being a superset of JavaScript, inherits its exception handling mechanisms but adds static type-checking that can help prevent errors before they occur.
Exception handling is all about anticipating potential errors in your code and managing them gracefully, rather than letting them crash your application.
Exception Handling in TypeScript: Different Methods Explained
Below, I will explain the various methods for handling exceptions in TypeScript.
Method 1: Using Try/Catch Blocks in TypeScript
The most fundamental way to handle exceptions in TypeScript is by using try/catch blocks. This approach allows you to capture runtime errors and respond appropriately.
Here’s how to implement basic exception handling:
function divideNumbers(a: number, b: number): number {
try {
if (b === 0) {
throw new Error("Cannot divide by zero!");
}
return a / b;
} catch (error) {
console.error("An error occurred:", error.message);
// Return a fallback value or rethrow the error
return Infinity; // Returning infinity as a fallback
} finally {
console.log("Division operation attempted");
// This block always executes, regardless of whether an exception occurred
}
}
// Usage
console.log(divideNumbers(10, 2)); // 5
console.log(divideNumbers(10, 0)); // InfinityThe try block contains code that might throw an exception, the catch block handles any errors, and the finally block contains code that always executes.

Method 2: Creating Custom Error Types in TypeScript
For more sophisticated applications, creating custom error types can help distinguish between different error scenarios and provide more context.
// Define custom error classes
class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = "ValidationError";
// This maintains proper stack traces in Node.js
Object.setPrototypeOf(this, ValidationError.prototype);
}
}
class DatabaseError extends Error {
constructor(message: string, public readonly code: number) {
super(message);
this.name = "DatabaseError";
Object.setPrototypeOf(this, DatabaseError.prototype);
}
}
// Usage
function processUserData(userData: any): void {
try {
if (!userData.name) {
throw new ValidationError("Name is required");
}
// Simulate database error
if (Math.random() < 0.5) {
throw new DatabaseError("Connection failed", 500);
}
console.log("Data processed successfully");
} catch (error) {
if (error instanceof ValidationError) {
console.error("Validation issue:", error.message);
// Handle validation errors
} else if (error instanceof DatabaseError) {
console.error(`Database error (${error.code}):`, error.message);
// Handle database errors, possibly retry
} else {
console.error("Unknown error:", error);
// Handle other unexpected errors
}
}
}This approach helps organize error handling in larger codebases and provides more informative error messages.

Method 3: Handling Async Exceptions with Try/Catch in TypeScript
When working with asynchronous code (which is common in TypeScript applications), you need to handle exceptions in promises and async/await functions.
// Using async/await with try/catch and public API
async function fetchUserData(userId: string): Promise<any | null> {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
if (error instanceof Error) {
console.error("Failed to fetch user data:", error.message);
} else {
console.error("An unknown error occurred while fetching user data.");
}
return null;
}
}
// Usage
async function displayUserProfile(userId: string): Promise<void> {
const userData = await fetchUserData(userId);
if (userData) {
console.log(`User Name: ${userData.name}`);
console.log(`Email: ${userData.email}`);
console.log(`City: ${userData.address.city}`);
} else {
console.log("Could not load user profile. Please try again.");
}
}
// Call the function
displayUserProfile("1"); // Try "2", "10", or an invalid ID like "100" to test

Method 4: Using Error Boundaries in React TypeScript Applications
If you’re working with React and TypeScript, error boundaries provide a way to catch JavaScript errors anywhere in a component tree and display a fallback UI.
// ErrorBoundary.tsx
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
fallback?: ReactNode;
children?: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}
interface State {
hasError: boolean;
error?: Error;
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
console.error("Error caught by boundary:", error, errorInfo);
}
render(): ReactNode {
if (this.state.hasError) {
return this.props.fallback ?? (
<div className="error-container">
<h2>Something went wrong.</h2>
<details>
<summary>Error Details</summary>
<p>{this.state.error?.toString()}</p>
</details>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Usage:
// App.tsx
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import UserProfile from './UserProfile';
function App() {
return (
<div className="app">
<h1>My TypeScript App</h1>
<ErrorBoundary
fallback={<p>Could not load user profile. Please try again later.</p>}
onError={(error) => {
// Send error to monitoring service
console.error("Logged to service:", error);
}}
>
<UserProfile userId="123" />
</ErrorBoundary>
</div>
);
}
export default App;Output:

Conclusion
Both methods work great – the try/catch approach is ideal for localized error handling, while custom error types provide better organization for larger applications. Global error handling ensures that no exception goes unnoticed, and React error boundaries provide elegant UI fallbacks.
I hope you found this article helpful. If you have any questions or suggestions, kindly leave them in the comments below. Remember, proper exception handling isn’t just about preventing crashes – it’s about creating a better user experience and making your code more maintainable.
Other TypeScript articles you may also like:
- TypeScript let vs var Difference
- Type vs Interface in TypeScript
- TypeScript: Check If a Key Exists in an Object
- Function Overloading in TypeScript

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.