10 Common TypeScript Mistakes (And How to Fix Them)
1. Using any Instead of unknown
// ❌ any disables all type checking — defeats the purpose
function parse(data: any) {
return data.name.toUpperCase() // no error, but crashes if name is missing
}
// ✅ unknown forces you to check before using function parse(data: unknown) { if (typeof data === 'object' && data !== null && 'name' in data) { return (data as { name: string }).name.toUpperCase() // safe } throw new Error('Invalid data shape') }
2. Incorrect Type Assertions
// ❌ Lying to TypeScript — runtime crash if response doesn't match User
const user = response as User
// ❌ Double assertion bypasses all safety const user = response as unknown as User
// ✅ Use a type guard to validate the shape function isUser(val: unknown): val is User { return typeof val === 'object' && val !== null && 'id' in val && 'name' in val } if (isUser(response)) { user.name // ✓ actually safe }
3. Not Enabling strictNullChecks
Without strictNullChecks, null and undefined are assignable to everything — eliminating half the value of TypeScript. Always add "strict": true to tsconfig.json.
// Without strictNullChecks — no error:
const user: User = null // TypeScript allows this
user.name // runtime crash
// With strictNullChecks — correctly catches it: const user: User | null = null user.name // ❌ Error: user is possibly null user?.name // ✅ safe
4. Object Literal Excess Property Checking Confusion
interface Point { x: number; y: number }
// ❌ TypeScript catches excess properties on direct assignment const p: Point = { x: 1, y: 2, z: 3 } // Error: z doesn't exist on Point
// But NOT through a variable (structural compatibility, not exact match): const obj = { x: 1, y: 2, z: 3 } const p2: Point = obj // ✓ No error — obj is structurally compatible
This is by design — structural typing means extra properties are fine as long as required ones are present.
5. Missing Generic Constraints
// ❌ T is unknown — you can only use T as unknown inside
function getValue<T>(obj: T, key: string) {
return obj[key] // Error: Element implicitly has 'any' type
}
// ✅ Constrain properly function getValue<T extends Record<string, unknown>, K extends keyof T>(obj: T, key: K): T[K] { return obj[key] }
6. Misusing Non-Null Assertion (!)
// ❌ Using ! everywhere is "runtime TypeScript" — you lose all protection
const el = document.getElementById('root')!
const user = getUser(id)!
const value = map.get(key)!
// ✅ Actually check for null const el = document.getElementById('root') if (!el) throw new Error('#root element not found') el.innerHTML = app // TypeScript knows el is non-null here
7. Ignoring TypeScript Errors with @ts-ignore
// ❌ Silences the error without fixing it
// @ts-ignore
user.nonExistentMethod()
// ✅ Use @ts-expect-error — fails if the error goes away (ensuring the comment stays intentional) // @ts-expect-error — legacy API, types not yet updated legacyLib.oldMethod()
// ✅ Or fix the root cause
8. Using Object as a Type
// ❌ Object and object are nearly useless
let data: Object = 5 // allows primitives!
let obj: object = { a: 1 } // can't access any properties
// ✅ Use Record, specific interface, or unknown let config: Record<string, unknown> = {} let user: User = { id: 1, name: 'Alice' }
9. Mutating Readonly Properties
interface Config { readonly apiKey: string }
const config: Config = { apiKey: 'secret' }
config.apiKey = 'other' // ❌ TypeScript error — readonly
// But: Readonly is SHALLOW interface State { readonly user: { name: string } } const state: State = { user: { name: 'Alice' } } state.user = { name: 'Bob' } // ❌ Error — user is readonly state.user.name = 'Bob' // ✓ No error — nested object is mutable // Use DeepReadonly<T> for deep immutability
10. Not Using Discriminated Unions
// ❌ All optional — TypeScript can't help narrow
interface ApiResponse {
data?: User
error?: string
loading?: boolean
}
// ✅ Discriminated union — exactly one state is possible at a time type ApiState<T> = | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: string }
function render(state: ApiState<User>) { if (state.status === 'success') { state.data.name // ✓ TypeScript knows data exists here } if (state.status === 'error') { state.error.toUpperCase() // ✓ TypeScript knows error exists here } }
Practice TypeScript at [JSPrep Pro](/auth).