TypeScript Tutorial
45 lessons
TypeScript: Type 'X' is Not Assignable to Type 'Y'
Type 'string | undefined' is not assignable to type 'string'
TypeScript's most frustrating error. Let's fix it properly.
What This Means
You're trying to put a value somewhere, but TypeScript says "nope, wrong type".
let name: string = getName(); // might return undefined
// Error: Type 'string | undefined' is not assignable to type 'string'
Common Scenarios
1. API Responses
interface User {
name: string;
email: string;
}
const user = await fetchUser(); // returns User | null
const name: string = user.name; // Error: user might be null
2. Array Methods
const users: User[] = [];
const firstUser: User = users[0]; // might be undefined
3. Object Properties
const config: { theme?: string } = {};
const theme: string = config.theme; // theme is optional
Fix #1: Type Guards
const user = await fetchUser();
if (user) {
const name: string = user.name; // safe now
}
TypeScript knows inside the if-block, user exists.
Fix #2: Non-Null Assertion (!)
const name: string = user!.name;
Tells TypeScript "trust me, this won't be null". Use carefully.
Only use if you're 100% sure. Otherwise, runtime error.
Fix #3: Nullish Coalescing
const name: string = user?.name ?? 'Default';
Provides fallback if undefined/null.
Fix #4: Type Assertion
const name = user.name as string;
Forces TypeScript to treat it as string. Dangerous - use only when you know better than TypeScript.
Fix #5: Update Type Definition
// Instead of
let name: string;
// Use
let name: string | undefined;
Accepts the reality that it might be undefined.
Real Example: Form Data
I had a form with optional fields:
interface FormData {
email: string;
phone?: string;
}
function sendData(email: string, phone: string) {
// API call
}
const form: FormData = getFormData();
sendData(form.email, form.phone); // Error on phone
Solution 1: Make phone optional in function
function sendData(email: string, phone?: string) {
// ...
}
Solution 2: Provide default
sendData(form.email, form.phone ?? '');
Solution 3: Type guard
if (form.phone) {
sendData(form.email, form.phone);
}
The 'any' Trap
Don't do this:
const user: any = await fetchUser();
You just disabled TypeScript. Defeats the purpose.
Union Types
type Status = 'pending' | 'success' | 'error';
let status: Status = 'completed'; // Error
TypeScript says: "completed" isn't one of the allowed values.
Fix: Use correct value
let status: Status = 'success';
Discriminated Unions
Better error handling:
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function handleResult(result: Result<User>) {
if (result.success) {
// TypeScript knows result.data exists here
console.log(result.data.name);
} else {
// TypeScript knows result.error exists here
console.log(result.error);
}
}
Generic Constraints
function getValue<T>(obj: T, key: string): string {
return obj[key]; // Error: can't index type T
}
Fix with constraint:
function getValue<T extends Record<string, string>>(
obj: T,
key: keyof T
): string {
return obj[key];
}
My Rule of Thumb
- Type Guard - First choice, safest
- Nullish Coalescing - When you have a sensible default
- Non-Null Assertion - Only if you're certain
- Type Assertion - Last resort
- any - Never (okay, very rarely)
Strict Mode
If you're not using strict mode:
{
"compilerOptions": {
"strict": true
}
}
Catches more errors early. Painful at first, saves you later.
Bottom Line
TypeScript errors are annoying but they prevent runtime bugs.
Don't fight TypeScript. Fix the actual type issue.
Took me months to stop using 'any' everywhere. Now my code has way fewer bugs.