TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
229: Specifying void as a Return Type
I've seen this happen to a lot of developers when they're moving from JavaScript to TypeScript. You write a function that's meant to perform a "side effect"—like logging a message, updating a global variable, or saving something to a database—and you just let TypeScript infer the return type. It feels fine until someone else (or you, three months from now) tries to use the result of that function.
The danger of the implicit return
function trackAnalyticsEvent(eventName: string, metadata: object) {
console.log(`Sending ${eventName} to server...`, metadata);
// Imagine a fetch call happens here
}
const response = trackAnalyticsEvent("button_click", { id: "submit-btn" });
if (response) {
console.log("Event tracked successfully!");
}
At first glance, this looks okay. But look closely at that if (response) block. Since trackAnalyticsEvent doesn't have a return statement, TypeScript infers its return type as void. In JavaScript, a function that doesn't return anything actually returns undefined.
The bug here is a logic error: the developer thinks trackAnalyticsEvent is returning a success boolean or a response object, but it's actually returning undefined. Because undefined is falsy, the "success" block will never execute, and you'll be scratching your head wondering why your analytics tracking isn't "working," even though the event is actually being sent.
Hardening the contract with void
To fix this, we don't change the logic—we change the contract. By explicitly specifying : void, you are telling every other developer on the team: "This function performs an action, but it provides no data back. Do not try to use its return value for anything."
function trackAnalyticsEvent(eventName: string, metadata: object): void {
console.log(`Sending ${eventName} to server...`, metadata);
}
const response = trackAnalyticsEvent("button_click", { id: "submit-btn" });
// TypeScript now throws a compile-time error here:
// "The left hand side of an 'if' condition should be an expression of type 'boolean'."
if (response) {
console.log("Event tracked successfully!");
}
Now, the bug is caught before the code ever runs. TypeScript sees that response is of type void and warns you that checking a void value in a conditional is nonsense. If you actually needed to know if the event was tracked, this error forces you to rethink the function signature—perhaps changing it to return a Promise<boolean>.
I'll be honest: some people argue that explicit void is redundant because TS infers it anyway. I disagree. In a professional codebase, explicit types act as documentation. When I see : void, I know the author intentionally designed it to return nothing, rather than just forgetting to write a return statement.
- Use
voidwhen the function's only purpose is to change something outside its own scope (a side effect). - Avoid using
voidif you might eventually need to return a status code or a success flag. - Remember that
voidis different fromnever;voidmeans "returns nothing," whilenevermeans "this function never actually finishes" (like a function that always throws an error).
📋 Practical Task
Fixing the User Notification Dispatcher
You are working on a notification system. One of the functions, notifyUser, is designed to simply trigger a UI alert. However, a teammate has tried to use it inside a loop to track how many notifications were "successfully" sent, leading to a bug where the counter never increments.
Your Task:
- Modify the
notifyUserfunction to explicitly specifyvoidas its return type. - Identify and remove the broken logic that attempts to use the return value of
notifyUser. - Ensure the code compiles without errors.
interface User {
id: string;
name: string;
}
function notifyUser(user: User, message: string) {
console.log(`Notification sent to ${user.name}: ${message}`);
}
const users: User[] = [
{ id: "1", name: "Alice" },
{ id: "2", name: "Bob" }
];
let notificationCount = 0;
users.forEach(user => {
const result = notifyUser(user, "Welcome to the platform!");
if (result) {
notificationCount++;
}
});
console.log(`Total notifications sent: ${notificationCount}`);There are no comments for now.