TypeScript adoption fails when teams try to migrate everything at once. The correct strategy is incremental — enable TypeScript alongside JavaScript, add types gradually, tighten strictness over time. This guide covers the migration path that actually works in production codebases, the tsconfig settings in the right order, and the utility types that replace the most common any escapes.
⚡ TL;DR: Start with allowJs + checkJs to get errors without rewriting. Rename .js to .ts one file at a time. Enable strict flags one by one. Use unknown over any. Master Partial, Required, Pick, Omit, Record, ReturnType, and Parameters — they replace 80% of any usage.
Step 1: Add TypeScript without breaking anything
// tsconfig.json — start permissive, tighten gradually
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"allowJs": true, // Let TypeScript coexist with JavaScript
"checkJs": false, // Don't error on .js files yet
"strict": false, // Start permissive
"noImplicitAny": false, // Allow implicit any for now
"outDir": "./dist",
"rootDir": "./src",
"sourceMap": true,
"declaration": true
},
"include": ["src/**/*"]
}
// At this stage: TypeScript compiles but doesn't complain
// Rename files from .js to .ts one at a time as you add types
Step 2: Enable strict flags one at a time
// Enable in this order (each catches a different bug class):
// Week 1: noImplicitAny — catch untyped parameters
// "noImplicitAny": true
// Error: function add(a, b) { return a + b; } — a and b need types
// Week 2: strictNullChecks — catch null/undefined bugs
// "strictNullChecks": true
// Error: const name = user.getName(); name.toUpperCase();
// user.getName() might return null!
// Week 3: strictFunctionTypes — catch callback type mismatches
// "strictFunctionTypes": true
// Week 4: strict: true — enables all remaining strict checks
// "strict": true
// Includes: strictBindCallApply, strictPropertyInitialization, noImplicitThis
// Final: tighten further
// "noUncheckedIndexedAccess": true -- arr[i] returns T | undefined
// "exactOptionalPropertyTypes": true -- explicit vs missing properties
Utility types that replace any
interface User { id: number; name: string; email: string; createdAt: Date; }
// Partial — all properties optional (update payloads)
type UpdateUser = Partial; // { id?: number; name?: string; ... }
// Required — all properties required (reverse Partial)
type RequiredUser = Required>;
// Pick — select specific properties
type UserPreview = Pick; // { id: number; name: string }
// Omit — exclude specific properties
type UserWithoutDates = Omit;
// Record — typed object/map
type RolePermissions = Record<'admin' | 'user' | 'guest', string[]>;
// ReturnType — extract function return type
type ApiResponse = ReturnType; // Inferred from function
// Parameters — extract function parameter types
type FetchParams = Parameters; // [id: string, options?: Options]
// Awaited — unwrap Promise type
type ResolvedUser = Awaited>; // User, not Promise
unknown vs any — always prefer unknown
// any: opt out of type checking — dangerous
function bad(value: any) {
value.toUpperCase(); // No error! Crashes at runtime if value is number
}
// unknown: must narrow before use — safe
function good(value: unknown) {
// value.toUpperCase(); // Error: unknown has no methods
if (typeof value === 'string') {
value.toUpperCase(); // Now TypeScript knows it's a string
}
}
// Use unknown for:
// - Function parameters you don't control (API responses, event handlers)
// - JSON.parse() return value (always returns any — cast to unknown first)
const raw: unknown = JSON.parse(apiResponse);
// Now narrow with type guard before using
TypeScript migration checklist
- ✅ Add TypeScript with allowJs: true — migrate files incrementally
- ✅ Enable strict flags one per week — each week fixes a new bug class
- ✅ Use unknown instead of any for external data
- ✅ Master utility types — they replace most any usage
- ✅ Add @ts-check comment to .js files for gradual type checking
- ✅ Use // @ts-ignore sparingly with a comment explaining why
- ❌ Never use any to silence TypeScript — it defeats the purpose
- ❌ Never migrate an entire codebase at once — incremental only
TypeScript migration builds on the TypeScript generics deep dive — once you’ve migrated, generics are the next major unlock. External reference: TypeScript official migration guide.
Recommended Reading
→ Designing Data-Intensive Applications — The essential book every senior developer needs. Covers distributed systems, databases, and production architecture.
→ The Pragmatic Programmer — Timeless engineering wisdom for writing better, more maintainable code at any level.
Affiliate links. We earn a small commission at no extra cost to you.
Free Weekly Newsletter
🚀 Don’t Miss the Next Cheat Code
Join 1,000+ senior developers getting expert-level JS, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
