TypeScript
A type system that catches mismatches, missing null checks, and incorrect call signatures before they reach runtime.
TypeScript's static analysis surfaces type mismatches, missing null checks, and incorrect function call signatures at write time rather than runtime. Paired with a well-tuned tsconfig, it enables IDE-driven rename refactors and structural navigation that actually hold across a large codebase.

Every project I start gets strict mode on day one — not as a box to tick, but because `strictNullChecks`, `noUncheckedIndexedAccess`, and `exactOptionalPropertyTypes` together eliminate the category of bugs I'd otherwise spend time debugging at runtime. For state modelling, I reach for discriminated unions over boolean flag combinations: a `{ status: 'loading' } | { status: 'error'; error: Error } | { status: 'success'; data: T }` union makes invalid states unrepresentable and lets the compiler enforce exhaustive handling in switch statements. At system boundaries — API responses, localStorage, third-party SDK callbacks — I type inputs as `unknown` and parse them explicitly rather than casting to `any` and hoping. Template literal types and conditional types earn their complexity only when they remove a meaningful class of runtime errors; otherwise I leave them out. When I migrate an existing JavaScript codebase, I start by enabling `allowJs` and `checkJs` together so tsc reports errors without requiring `.ts` extensions, rename files one module at a time starting from the lowest-dependency leaves, and tighten the strictness flags in a second pass once the coverage is there.
tsconfig Audit
I go through each strict flag individually — `strictNullChecks`, `noImplicitAny`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes` — rather than flipping the `strict` umbrella and walking away. I also look at `moduleResolution` (Node16 vs Bundler), whether `skipLibCheck` is hiding real declaration errors, and whether `paths` aliases are configured consistently with the bundler so IDE go-to-definition actually works.
Type Coverage Improvement
I start at system boundaries: API responses and external inputs become `unknown` with explicit parse functions rather than `any` casts. Inside the codebase, I replace boolean-flag state (`isLoading`, `isError`, `isSuccess`) with discriminated unions so the compiler enforces the shape at every consumption site. Where a value needs to satisfy an interface without losing its inferred type, I use the `satisfies` operator instead of a type annotation.
JavaScript Migration
The sequence I follow: enable `allowJs` and `checkJs` first so tsc reports errors across the existing JS files without requiring renaming anything; fix the surfaced errors; then rename files to `.ts` starting from the modules with fewest dependents and working inward; finally tighten the strictness flags file-by-file rather than all at once, so there's never a point where the build is completely broken.
Large Frontend Applications
React apps with dozens of components benefit specifically from TypeScript's structural typing and inference: prop types flow through component trees without redundant annotations, and the compiler catches mismatched event handler signatures and missing required props across the whole tree.
Shared Backend/Frontend Types
In a monorepo I put shared interfaces in a dedicated package that both the API and the client import. When an API response shape changes, the TypeScript error surfaces in the consuming client code at compile time rather than as a runtime 404 or undefined-property access.
Library & SDK Development
Packages that ship `.d.ts` declaration files give consumers full type inference without needing access to the source. Conditional types and generic constraints let the return type of a function depend on the shape of its arguments — so callers get the narrowest possible type instead of a broad union.
Let's talk TypeScript.
No pitch. Just a technical conversation about the problem you're working on.