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
123: When to Use unknown Instead of any
I've seen this happen in almost every professional codebase I've joined: a developer is dealing with a complex API response or a third-party library that doesn't have great types, and they get frustrated. They reach for any because it makes the compiler stop complaining. It feels like a shortcut, but in reality, it's a landmine.
Today, I want to show you why unknown is almost always the better choice when you truly don't know what the data is. While both any and unknown tell TypeScript "this could be anything," they handle the consequences of that uncertainty very differently.
The temptation of the any escape hatch
Let's build a simple utility that processes a user profile from a legacy API. These old APIs are notorious for sending back inconsistent data—sometimes a field is a string, sometimes it's null, and sometimes it's missing entirely.
function processUserProfile(data: any) {
console.log(`Processing user: ${data.username}`);
return data.username.toUpperCase();
}
// This works fine...
processUserProfile({ username: 'jdoe' });
// But this crashes at runtime, and TypeScript didn't warn me!
processUserProfile(null);
Here's where I messed up. By using any, I essentially told TypeScript, "Trust me, I know what I'm doing. Turn off all type checking for this variable." The compiler let me call .toUpperCase() on data.username without checking if data even existed or if username was actually a string. I just traded a compile-time error for a runtime crash.
Switching to unknown for better safety
If I replace any with unknown, the experience changes immediately. unknown is the type-safe sibling of any. It says, "This could be anything, so I'm not going to let you do anything with it until you prove what it is."
function processUserProfile(data: unknown) {
// TypeScript now throws an error here:
// "Object is of type 'unknown'"
console.log(`Processing user: ${data.username}`);
return data.username.toUpperCase();
}
Now the compiler is actually doing its job. It's refusing to let me access username because it doesn't know if data is an object, a string, or a number. It's forcing me to be honest about the uncertainty of the data.
Proving the type through narrowing
To make this work, we have to use "Type Narrowing." We need to verify the data at runtime before we use it. This is where we move from "hoping the data is correct" to "guaranteeing the data is correct."
interface User {
username: string;
}
function processUserProfile(data: unknown) {
// 1. Check if data is an object and not null
if (typeof data !== 'object' || data === null) {
throw new Error("Invalid data: Expected an object");
}
// 2. Cast to 'any' briefly or use a type guard to check the property
// Here, I'll use a simple type check for the property
if ('username' in data && typeof (data as any).username === 'string') {
const user = data as User; // Now it's safe to cast
return user.username.toUpperCase();
}
throw new Error("Invalid data: Missing username string");
}
I'll be honest: the unknown version takes more lines of code. It's more verbose. But as a software engineer, I'd rather spend an extra two minutes writing a type guard than spend two hours debugging a TypeError: Cannot read property 'toUpperCase' of undefined in production at 3 AM.
The mental shift from any to unknown
The rule of thumb I use is this: use any only when you are migrating a massive JS project to TS and you literally don't have time to type it, or when you're writing a very generic utility where type safety is mathematically impossible. For everything else—API responses, localStorage reads, JSON.parse results—use unknown. It shifts the burden of proof from the compiler to the developer, which is exactly where it should be when dealing with external data.
📋 Practical Task
Refactoring the Weather API Parser
You've inherited a piece of code that parses a weather report from a third-party API. The original developer used any, and it's causing intermittent crashes because the API sometimes returns an error object instead of the weather data.
Your Task: Refactor the parseWeatherReport function to use unknown instead of any. You must implement a runtime check to ensure that the data object contains a temperature property that is a number before attempting to log it. If the data is invalid, the function should return the string "Invalid weather data".
// Current broken implementation
function parseWeatherReport(data: any) {
return `The temperature is ${data.temperature} degrees`;
}
// Test cases to handle:
console.log(parseWeatherReport({ temperature: 22 })); // Should work
console.log(parseWeatherReport({ error: "City not found" })); // Should return "Invalid weather data"
console.log(parseWeatherReport(null)); // Should return "Invalid weather data"
There are no comments for now.