TypeScript Without a Senior: 15 Prompts That Turn an LLM Into Your Type Cop, Migration Guide, and Code Reviewer

TypeScript in 2026 is no longer a nice-to-have — it is the default for serious web work. But between strict mode, generic gymnastics, and the joy of migrating a 40k-line JavaScript codebase, plenty of teams still ship without a senior who can untangle TS2589: Type instantiation is excessively deep. The good news: an LLM, prompted correctly, covers most of that gap.

This is not a list of "ask AI to write code." These are prompts I actually reuse — for squeezing real types out of any, planning JS→TS migrations, and getting a second pair of eyes on a PR before it hits main. Each one solves a specific pain, ships with a working example, and assumes you paste real context.

The rule that makes all of them work: give the model the compiler version, tsconfig.json flags, and the actual error text. Vague prompts get vague types.

The Prompts

1. Turn any into a real type from usage

Task: replace a lazy any with a type inferred from how the value is actually used.

You are a TypeScript expert (TS 5.x, strict: true).
Below is a function using `any`. Infer the narrowest correct
type from all usages. Return ONLY the typed function plus a
1-line note on any assumption you made.
function parseConfig(raw: any) {
  return { port: Number(raw.port), debug: raw.debug === true };
}
// => raw: { port: string | number; debug?: unknown }

2. Discriminated unions instead of boolean soup

Task: collapse parallel booleans into a tagged union the compiler can exhaust.

Refactor these booleans into a discriminated union with a
`status` tag. Add an exhaustive switch with `never` check.
type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "error"; message: string };

function assertNever(x: never): never { throw new Error(String(x)); }

Exhaustiveness checks catch the bug where you add a state and forget a branch.

3. Decode external JSON safely

Task: stop trusting fetch().json() — it returns any.

Write a runtime validator for this API response using zod.
Derive the TS type from the schema. Show a parse with
safeParse and error handling.
import { z } from "zod";
const User = z.object({ id: z.number(), email: z.string().email() });
type User = z.infer<typeof User>;
const res = User.safeParse(await r.json());
if (!res.success) throw new Error(res.error.message);
Tip Always ask for the schema and the inferred type in one shot. Two prompts = two sources of truth that drift.

4. Diagnose the scary compiler errors

Task: translate cryptic errors into a fix.

Error: TS2589 Type instantiation is excessively deep.
Code: <paste>. tsconfig: <paste>. Explain the cause,
give the minimal fix, and note any perf trade-off.

Common real causes: recursive conditional types without a depth guard, or z.infer on a self-referential schema. The fix is usually a manual type annotation that breaks the inference loop.

5. Plan a JS → TS migration in stages

Task: avoid a big-bang rewrite.

I have a JS repo. Produce a staged migration plan:
1) tsconfig with allowJs + checkJs,
2) file order by dependency graph,
3) which files get `// @ts-expect-error` as temporary debt,
4) exit criteria per stage.

Practical default: "strict": true from day one on new files, "noImplicitAny": false on legacy until each module is clean. Track debt with tsc --noEmit in CI.

6. Generics that don't explode

Task: constrain a generic so it accepts only valid keys.

Write getByKey<T, K extends keyof T>(obj: T, key: K): T[K].
No `any`. Show a call that fails to compile when key is wrong.
function getByKey<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

7. Type-level test in one prompt

Task: prove the types actually reject bad input.

Generate type tests using expect-type for this interface.
Include one @ts-expect-error case that must fail.
import { expectTypeOf } from "expect-type";
expectTypeOf(getByKey({ a: 1 }, "a")).toEqualTypeOf<number>();
// @ts-expect-error wrong key must fail
// getByKey({ a: 1 }, "b");

8. PR review with a strict checklist

Task: a reviewer that flags real risks, not style.

Review this diff as a senior TS reviewer. Flag: unsafe
casts, missing null checks, `any` leaks, unhandled
promise rejections, and API-breaking type changes.
Output a table: file

| issue | severity | fix.
Key point A structured table output forces the model to be specific. "Looks good" reviews disappear when you demand file + line + fix.

9. Wire up strict ESLint for types

Task: let the linter enforce what reviewers miss.

Configure typescript-eslint with no-explicit-any,
no-floating-promises, and consistent-type-imports.
Give flat config (eslint.config.js) for ESLint 9.

no-floating-promises alone catches a class of async bugs reviewers regularly skim past.

10. Narrow unknown in catch blocks

Task: catch (e) is unknown under strict — handle it.

Write a toError(e: unknown): Error helper and rewrite
this try/catch to use it. No `as any`.
function toError(e: unknown): Error {
  return e instanceof Error ? e : new Error(String(e));
}

11. Contract tests for API types

Task: keep frontend and backend types honest.

Given this OpenAPI spec, generate a TypeScript client
with typed endpoints and a test that fails if a field
is renamed. Use openapi-typescript.

12. Refactor to satisfies

Task: validate a config object without widening its type.

Rewrite this config to use `satisfies` so keys stay
literal but values are checked against Config.
const cfg = { retries: 3, mode: "fast" } satisfies Config;

13. Explain a type to a teammate

Task: onboarding aid for gnarly generics.

Explain this type in plain English, then show 3 call
examples: one valid, one invalid, one edge case.

14. Generate a tsconfig for a real target

Task: stop copy-pasting random configs.

Give a strict tsconfig for a Node 22 + ESM project
using tsc for build. Explain module/moduleResolution.

15. Review a migration PR for regressions

Task: catch the @ts-expect-error that hides a real bug.

List every @ts-expect-error and as-cast in this diff.
For each, state whether it hides a genuine bug and
what the safe alternative is.

Putting It Together

The pattern across all fifteen: constrain the model with your real compiler settings and demand structured output. That combination turns a generic chatbot into a type-aware collaborator. It will not replace architectural judgment — someone still decides the domain model — but it removes the daily friction of any, migration chaos, and PRs that ship because nobody reviewed the types.

Start with prompts 1, 4, and 8 on your current branch. Paste your tsconfig.json, run tsc --noEmit, and let the compiler be the judge. If a suggested type compiles under strict and passes a type test, you have earned a real improvement — and a second pair of eyes that never gets tired.

← All posts

Comments