cheat sheet

Common TypeScript Errors

A reference for the most frequent TypeScript compiler errors — their meaning, a minimal reproduction, and the correct fix. Covers TS2304, TS2345, TS2339, TS2322, TS2532, TS2554, TS2307, TS2366, TS7006, TS2571, TS2769, TS2693, plus type assertions and ts-expect-error.

Common TypeScript Errors

What it is

A practical reference for the TypeScript compiler errors encountered most often in real codebases. Each entry shows the error code, what it means, a minimal reproduction, and the correct fix — not just a cast or suppression.


TS2304 — Cannot find name

Meaning: TypeScript cannot find a name in the current scope. Usually caused by a missing import, missing @types package, or a typo.

typescript
// Error
const result = formatDate(new Date()); // TS2304: Cannot find name 'formatDate'

Fix — import the symbol:

typescript
import { formatDate } from "date-fns";
const result = formatDate(new Date(), "yyyy-MM-dd");

Fix — install missing type definitions:

bash
npm install -D @types/node
# Then use: process.env, Buffer, __dirname, etc.

Output: (none — exits 0 on success)

Fix — declare the global in a .d.ts file:

typescript
// global.d.ts
declare const __APP_VERSION__: string;

TS2345 — Argument of type X is not assignable to parameter of type Y

Meaning: You are passing a value of the wrong type to a function or method.

typescript
function greet(name: string): string {
  return `Hello, ${name}`;
}

greet(42); // TS2345: Argument of type 'number' is not assignable to parameter of type 'string'

Fix — pass the correct type:

typescript
greet("Alice"); // OK
greet(String(42)); // OK — explicit conversion

Common variant — union type not narrowed:

typescript
function process(val: string | null): void {
  sendToApi(val); // TS2345 — null not assignable to string
}

// Fix — narrow first
function process(val: string | null): void {
  if (val === null) return;
  sendToApi(val); // val: string
}

TS2339 — Property does not exist on type

Meaning: You are accessing a property that TypeScript does not know exists on the type.

typescript
interface User { id: number; name: string }

const user: User = { id: 1, name: "Alice" };
console.log(user.email); // TS2339: Property 'email' does not exist on type 'User'

Fix — add the property to the type:

typescript
interface User { id: number; name: string; email?: string }

Fix — use type narrowing or a type guard:

typescript
function getEmail(obj: unknown): string | undefined {
  if (typeof obj === "object" && obj !== null && "email" in obj) {
    return (obj as { email: string }).email;
  }
}

Common variant — accessing property on union before narrowing:

typescript
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };

function area(s: Shape): number {
  return s.radius * Math.PI; // TS2339 — radius doesn't exist on Square
}

// Fix — narrow first
function area(s: Shape): number {
  if (s.kind === "circle") return s.radius * Math.PI;
  return s.side ** 2;
}

TS2322 — Type X is not assignable to type Y

Meaning: A value or expression is being assigned to a variable or returned from a function whose type is incompatible.

typescript
const count: number = "five"; // TS2322: Type 'string' is not assignable to type 'number'

function getCount(): number {
  return "five"; // TS2322
}

Fix — use the correct type or widen the target:

typescript
const count: number = 5;
const countOrLabel: number | string = "five"; // OK

Common variant — object literal with extra properties:

typescript
interface Point { x: number; y: number }

const p: Point = { x: 1, y: 2, z: 3 }; // TS2322 — excess property 'z'

// Fix — remove the extra property
const p: Point = { x: 1, y: 2 };

// Fix — widen the type
interface Point3D extends Point { z: number }
const p: Point3D = { x: 1, y: 2, z: 3 };

TS2532 — Object is possibly undefined

Meaning: You are accessing a property or calling a method on a value that might be undefined. Requires strictNullChecks: true.

typescript
const arr = [1, 2, 3];
const first = arr[0]; // first: number | undefined (with noUncheckedIndexedAccess)
console.log(first.toFixed(2)); // TS2532: Object is possibly 'undefined'

Fix — narrow with a null check:

typescript
if (first !== undefined) {
  console.log(first.toFixed(2)); // OK
}

// Or use optional chaining
console.log(first?.toFixed(2)); // OK — returns undefined if first is undefined

Fix — use the non-null assertion operator (only when you are certain):

typescript
console.log(first!.toFixed(2)); // OK — you assert first is not undefined

The non-null assertion ! is a promise to TypeScript, not a runtime check. If the value actually is undefined, you will get a runtime error. Only use it when you are certain the value exists and have a comment explaining why.


TS2554 — Expected N arguments, but got M

Meaning: You are calling a function with the wrong number of arguments.

typescript
function greet(name: string, greeting: string): string {
  return `${greeting}, ${name}!`;
}

greet("Alice"); // TS2554: Expected 2 arguments, but got 1

Fix — provide all required arguments:

typescript
greet("Alice", "Hello");

Fix — make the second argument optional:

typescript
function greet(name: string, greeting = "Hello"): string {
  return `${greeting}, ${name}!`;
}

greet("Alice"); // OK — greeting defaults to "Hello"

Fix — use rest parameters if arity varies:

typescript
function log(...messages: string[]): void {
  console.log(messages.join(" "));
}

log("a", "b", "c"); // OK

TS2307 — Cannot find module or its corresponding type declarations

Meaning: TypeScript cannot resolve the module path, or the module exists but has no type declarations.

typescript
import { parse } from "some-library"; // TS2307: Cannot find module 'some-library'

Fix — install the package:

bash
npm install some-library

Output: (none — exits 0 on success)

Fix — install type declarations:

bash
npm install -D @types/some-library

Output: (none — exits 0 on success)

Fix — write your own declaration if no types exist:

typescript
// some-library.d.ts
declare module "some-library" {
  export function parse(input: string): unknown;
}

Fix — check path aliases in tsconfig if it is a relative import:

json
{
  "compilerOptions": {
    "paths": { "@/*": ["src/*"] }
  }
}

TS2366 — Function lacks ending return statement and return type does not include undefined

Meaning: Not all code paths in a function return a value, but the return type does not allow undefined. Enabled by noImplicitReturns: true.

typescript
function getLabel(code: number): string {
  if (code === 200) return "OK";
  if (code === 404) return "Not Found";
  // TS2366 — what if code is 500?
}

Fix — add a return on all paths:

typescript
function getLabel(code: number): string {
  if (code === 200) return "OK";
  if (code === 404) return "Not Found";
  return "Unknown";
}

Fix — include undefined in the return type:

typescript
function getLabel(code: number): string | undefined {
  if (code === 200) return "OK";
  if (code === 404) return "Not Found";
}

Fix — use exhaustive checking with never:

typescript
type StatusCode = 200 | 404 | 500;

function getLabel(code: StatusCode): string {
  switch (code) {
    case 200: return "OK";
    case 404: return "Not Found";
    case 500: return "Internal Server Error";
  }
}
// TypeScript knows all cases are covered — no default needed

TS7006 — Parameter implicitly has an 'any' type

Meaning: A function parameter has no type annotation and TypeScript cannot infer it. Requires noImplicitAny: true (included in strict).

typescript
function double(x) { // TS7006: Parameter 'x' implicitly has an 'any' type
  return x * 2;
}

Fix — annotate the parameter:

typescript
function double(x: number): number {
  return x * 2;
}

Common variant — callbacks:

typescript
[1, 2, 3].reduce((acc, val) => acc + val, 0); // OK — inferred from array type

const fn = (x) => x * 2; // TS7006 — cannot infer x
const fn = (x: number) => x * 2; // OK

TS2571 — Object is of type 'unknown'

Meaning: You are trying to access properties or call methods on a value typed as unknown, which TypeScript treats as the safe top type. You must narrow unknown before using it.

typescript
async function fetchData(): Promise<unknown> {
  return (await fetch("/api/data")).json();
}

const data = await fetchData();
console.log(data.name); // TS2571: Object is of type 'unknown'

Fix — narrow with a type guard:

typescript
if (typeof data === "object" && data !== null && "name" in data) {
  console.log((data as { name: string }).name);
}

Fix — use Zod or a manual type guard:

typescript
import { z } from "zod";
const schema = z.object({ name: z.string() });
const parsed = schema.parse(data);
console.log(parsed.name); // OK

Fix — assert with as only when you are certain of the shape:

typescript
const typed = data as { name: string };
console.log(typed.name);

unknown is the correct type for data from external sources (API responses, JSON parsing, try/catch errors). It forces you to validate before use, which is the right behavior. Prefer unknown over any in all new code.


TS2769 — No overload matches this call

Meaning: A function has multiple overloads and none of them match the arguments provided.

typescript
function format(value: string): string;
function format(value: number, precision: number): string;
function format(value: string | number, precision?: number): string {
  if (typeof value === "number") return value.toFixed(precision ?? 2);
  return value.trim();
}

format(42);           // TS2769 — number overload requires precision argument
format(42, 2);        // OK
format("hello");      // OK

Fix — provide required arguments for the matching overload:

typescript
format(42, 2);   // matches: format(value: number, precision: number): string
format("hello"); // matches: format(value: string): string

Fix — adjust overload signatures if the restriction is wrong:

typescript
function format(value: string): string;
function format(value: number, precision?: number): string; // precision now optional
function format(value: string | number, precision = 2): string {
  if (typeof value === "number") return value.toFixed(precision);
  return value.trim();
}

format(42);    // OK now
format(42, 3); // OK

TS7053 — Element implicitly has an 'any' type because expression of type X can't be used to index type Y

Meaning: You are indexing an object with a key the compiler can't prove is one of its own keys. Often a plain string index over an object whose keys are a specific literal union. Triggered by noImplicitAny: true.

typescript
const labels = { en: "Hello", fr: "Bonjour", es: "Hola" };

function greet(lang: string): string {
  return labels[lang]; // TS7053 — string can't index { en, fr, es }
}

Fix — narrow the key to the literal union:

typescript
type Lang = keyof typeof labels;

function greet(lang: Lang): string {
  return labels[lang];
}

Fix — assert after a runtime check:

typescript
function greet(lang: string): string {
  if (lang in labels) {
    return labels[lang as keyof typeof labels];
  }
  return "Hello";
}

Fix — give the object a wider index signature:

typescript
const labels: Record<string, string> = { en: "Hello", fr: "Bonjour", es: "Hola" };
function greet(lang: string): string { return labels[lang] ?? "Hello"; }

The trade-off: a Record<string, string> index signature loses the precise key union. Prefer narrowing the input when you can.


TS18046 — X is of type 'unknown'

Meaning: Closely related to TS2571 but raised in catch-clauses since TS 4.4 changed the default catch parameter type from any to unknown. You must narrow the error before using it.

typescript
try {
  JSON.parse("not json");
} catch (e) {
  console.log(e.message); // TS18046 — 'e' is of type 'unknown'
}

Fix — narrow with instanceof Error:

typescript
try {
  JSON.parse("not json");
} catch (e) {
  if (e instanceof Error) {
    console.log(e.message);
  } else {
    console.log("unknown error:", e);
  }
}

Fix — extract once with a typed helper:

typescript
function errorMessage(e: unknown): string {
  if (e instanceof Error) return e.message;
  if (typeof e === "string") return e;
  return JSON.stringify(e);
}

try { /* ... */ } catch (e) { console.log(errorMessage(e)); }

Fix — explicit : any on the catch (legacy, discouraged):

typescript
try { /* ... */ } catch (e: any) { console.log(e.message); }

The : any annotation is the only place TypeScript lets you opt into any inside a catch; it survives noImplicitAny because you're being explicit.


TS2740 — Type X is missing the following properties from type Y

Meaning: You are assigning an object to a variable typed as Y, but the object lacks one or more required properties. Common when adapting between schema versions or partial mocks.

typescript
interface User {
  id: number;
  name: string;
  email: string;
}

const u: User = { id: 1, name: "Alice" };
// TS2740 — Type '{ id: number; name: string; }' is missing the following
//   properties from type 'User': email

Fix — supply the missing properties:

typescript
const u: User = { id: 1, name: "Alice", email: "alice@example.com" };

Fix — make the missing fields optional in the type:

typescript
interface User {
  id: number;
  name: string;
  email?: string;
}

Fix — use Partial<User> if every field should be optional in this context:

typescript
const u: Partial<User> = { id: 1, name: "Alice" }; // OK

Fix — use as only when you're producing a deliberately partial test mock:

typescript
const u = { id: 1 } as User; // explicit shortcut — write a comment explaining why

TS2741 — Property X is missing in type Y but required in type Z

Meaning: A variant of TS2740 raised for a single missing property — the compiler picked the first missing required field to report. Same fixes apply.

typescript
interface Config { host: string; port: number; tls: boolean }

const c: Config = { host: "localhost", port: 3000 };
// TS2741 — Property 'tls' is missing in type ... but required in type 'Config'

Fix — same options as TS2740:

typescript
const c: Config = { host: "localhost", port: 3000, tls: false };  // supply it
// or make tls optional:  tls?: boolean

TS2531 — Object is possibly 'null'

Meaning: Twin of TS2532 but for the null half of null | T. Same narrowing patterns apply.

typescript
const el: HTMLElement | null = document.querySelector(".btn");
el.click(); // TS2531

Fix — narrow:

typescript
if (el !== null) el.click();
// or
el?.click();

TS2552 — Cannot find name X. Did you mean Y?

Meaning: Typo. The compiler caught a close-match symbol in scope and is suggesting it. Always read the suggestion — it's correct ~95% of the time.

typescript
const arr = [1, 2, 3];
console.log(arr.lenght); // TS2552: Did you mean 'length'?

Fix — accept the suggestion:

typescript
console.log(arr.length);

TS2367 — This comparison appears to be unintentional because the types X and Y have no overlap

Meaning: You are using === or !== to compare values whose types have no possible overlap — the comparison is always false. Almost always a real bug.

typescript
function isReady(status: "loading" | "done"): boolean {
  return status === "ready"; // TS2367 — "ready" can never equal "loading" | "done"
}

Fix — use a value actually in the union:

typescript
function isReady(status: "loading" | "done"): boolean {
  return status === "done";
}

Fix — widen the type if "ready" is genuinely a possible state:

typescript
type Status = "loading" | "done" | "ready";

TS2786 — X cannot be used as a JSX component

Meaning: Specific to React projects — typically caused by mismatched @types/react versions between your app and a library, or by a component returning a type React rejects (e.g. Promise<JSX.Element> outside a Server Component).

Fix — align React types:

bash
npm install -D @types/react@^18 @types/react-dom@^18

Output: (none — exits 0 on success)

Fix — ensure pinned versions in pnpm workspaces:

json
{
  "pnpm": {
    "overrides": {
      "@types/react": "^18.2.0",
      "@types/react-dom": "^18.2.0"
    }
  }
}

TS2459 — Type X has no property Y and no string index signature

Meaning: You're trying to read a property the compiler can't see on a type — typically a destructuring assignment from an opaque value.

typescript
function f(opts: { a: number }): void {
  const { a, b } = opts; // TS2459 — b doesn't exist
}

Fix — add the property or constrain the input:

typescript
function f(opts: { a: number; b?: number }): void {
  const { a, b } = opts;
}

TS2614 — Module X has no exported member Y. Did you mean to use 'import Y from X' instead?

Meaning: You did import { foo } from "lib" but foo is the default export. Common when migrating between CommonJS and ESM libraries.

typescript
// Error
import { React } from "react";

// Fix
import React from "react";
// or, ESM-friendly:
import * as React from "react";

TS2576 — Property X is a static member of type Y

Meaning: You're trying to access a static member through an instance, or vice versa.

typescript
class HttpClient {
  static DEFAULT_TIMEOUT = 5000;
  baseUrl = "/";
}

const c = new HttpClient();
console.log(c.DEFAULT_TIMEOUT); // TS2576

// Fix — use the class, not the instance
console.log(HttpClient.DEFAULT_TIMEOUT);

TS2693 — X only refers to a type, but is being used as a value here

Meaning: You are trying to use a type alias or interface as a runtime value (e.g., calling new MyType() or passing it as an argument).

typescript
type Config = { host: string; port: number };

const config = new Config(); // TS2693: 'Config' only refers to a type, but is being used as a value

Fix — use a class instead of a type alias if you need a constructor:

typescript
class Config {
  constructor(public host: string, public port: number) {}
}

const config = new Config("localhost", 3000); // OK

Fix — if you need both a type and a constructor, use a class with an interface:

typescript
interface IConfig { host: string; port: number }

class Config implements IConfig {
  constructor(public host: string, public port: number) {}
}

const config: IConfig = new Config("localhost", 3000);

Type assertion patterns

as Type

Overrides TypeScript's inferred type. Use when you have more information than TypeScript can infer, typically at runtime boundaries.

typescript
const el = document.getElementById("app") as HTMLDivElement;
el.style.backgroundColor = "red"; // OK — HTMLDivElement has .style

const data = JSON.parse(raw) as { name: string; age: number };

When it is appropriate:

  • DOM queries where you know the element type
  • Values from JSON.parse when you have validated the shape
  • Casting after Object.keys() or Object.entries() which TypeScript types loosely

When it is a smell:

  • Silencing legitimate type errors instead of fixing the underlying issue
  • Casting unknown to a specific type without validating

as unknown as Type

Double assertion. Bypasses all type checking. Necessary in rare scenarios where two types are unrelated.

typescript
// Legitimate: converting a mocked object in tests
const req = {} as unknown as Request;

// Legitimate: interop with a legacy API that returns a poorly typed value
const typed = (legacyValue as unknown) as MyNewType;

as unknown as Type is a last resort. If you find yourself using it frequently, it usually signals a design problem — a missing type definition, an overly strict return type, or a missing overload.


ts-expect-error vs ts-ignore

Both suppress the next TypeScript error on the following line, but they behave differently when the error disappears.

ts-ignore

Suppresses the error unconditionally. If the error is later fixed, the suppression silently remains with no warning.

typescript
// @ts-ignore
const x: number = "hello"; // suppressed — no error

ts-expect-error

Asserts that the next line will produce an error. If no error is produced (e.g., after an upgrade that fixed the issue), TypeScript reports an "unused ts-expect-error" warning.

typescript
// @ts-expect-error — TS2322: string not assignable to number (intentional test)
const x: number = "hello";

Rule of thumb: Always prefer @ts-expect-error over @ts-ignore. It self-documents the intent and alerts you when the suppression is no longer needed.

Use cases for @ts-expect-error:

  • Testing that certain type combinations are correctly rejected
  • Temporary workarounds for third-party type bugs (with a link to the issue)
  • Overriding type definitions in test files

Type-check only without emitting

Running tsc --noEmit validates types without writing any .js output. This is the standard way to gate CI on type correctness.

bash
npx tsc --noEmit

Output (no errors):

text
(no output — exit code 0)

Output (with errors):

text
src/api.ts:12:5 - error TS2339: Property 'userId' does not exist on type 'Request'.

12     const id = req.userId;
                      ~~~~~~

src/utils.ts:8:3 - error TS2345: Argument of type 'string | null' is not assignable to parameter of type 'string'.

8   processInput(value);
    ~~~~~~~~~~~~~~~~~~~~

Found 2 errors.

Use in CI:

yaml
- name: Type check
  run: npx tsc --noEmit

The exit code is 0 on success and non-zero on any type error, making it compatible with all CI systems.

For Vite or esbuild projects where noEmit: true is already set in tsconfig.json, just run npx tsc without any flag — it is equivalent.

TS error code quick-reference

A categorised cheat-sheet of the errors covered above plus a few more you'll see less often. Use it to identify which section of this article (or which neighbouring article) to consult.

Module / import errors

CodeMeaningSection
TS2304Cannot find nameabove
TS2307Cannot find moduleabove
TS2614Module has no exported memberabove
TS2552Cannot find name — did you meanabove
TS6133Declared but never usedenable noUnusedLocals / noUnusedParameters
TS1005 / TS1109Syntax — expected tokenusually a real parse error; check the location

Type-checking errors

CodeMeaningSection
TS2322Type X not assignable to Yabove
TS2339Property does not existabove
TS2345Argument type mismatchabove
TS2740Missing multiple propertiesabove
TS2741Missing a single propertyabove
TS2367Comparison has no overlapabove
TS2769No overload matchesabove
TS2353Object literal has unknown propertyexcess-property check on a fresh object literal
TS2459No property + no index signatureabove

Null / undefined narrowing

CodeMeaningSection
TS2531Possibly nullabove
TS2532Possibly undefinedabove
TS18047Property may be undefined (with noUncheckedIndexedAccess)narrow array access
TS18048Property may be undefinednarrow optional access

unknown / never / any

CodeMeaningSection
TS2571Object is of type unknownabove
TS18046X is of type unknown (catch clauses)above
TS7006Parameter implicitly anyabove
TS7053Element implicitly any (index expression)above
TS2538Type cannot be used as an index typeunion/symbol used where literal/string/number expected

Function signatures

CodeMeaningSection
TS2554Wrong arityabove
TS2366Function lacks return statementabove
TS2769No overload matchesabove
TS2576Static member accessed on instanceabove

Type-vs-value confusion

CodeMeaningSection
TS2693Used as value but is a typeabove
TS2749Refers to value but is being used as typeinverse of TS2693 — needs typeof X

Framework / ecosystem

CodeMeaningSection
TS2786Not a valid JSX componentabove
TS2742Inferred type cannot be namedneeds an explicit annotation (often from monorepo cross-package refs)

Common pitfalls

  1. Reaching for as first — Most type errors signal a real shape mismatch. A cast hides the symptom; narrow or fix the type instead. Reserve as for runtime boundaries.
  2. @ts-ignore left in long-term code — Use @ts-expect-error so the suppression itself complains when no longer needed.
  3. Silencing any with // @ts-ignore — The error is the warning; deal with it. If you must, switch to unknown and narrow.
  4. Ignoring TS2367 — The compiler can prove the comparison is always false; the code is almost certainly buggy.
  5. Letting TS7053 spread — One quiet implicit any in an indexer cascades into many. Narrow the key once at the boundary.
  6. Casting through unknown(x as unknown) as MyType is a last resort. If you write it twice, you have a design problem.
  7. Ignoring TS18046 — Catch-clause errors are unknown for a reason. Narrow with instanceof Error or run them through a typed extractor.
  8. noImplicitAny: false — Disabling this defeats most of TypeScript's value. Keep strict: true from project day one.
  9. Skipping the suggestion in TS2552 — The compiler's spelling suggestion is right most of the time; take it.
  10. Believing Omit is strictOmit<T, "missing-key"> returns T unchanged. Use Except from type-fest for actual checking.

Real-world recipes

Recipe 1 — Centralised error normaliser

A single helper that converts any thrown value into a normalised Error. Lifts TS18046 friction out of every catch block.

typescript
function toError(e: unknown): Error {
  if (e instanceof Error) return e;
  if (typeof e === "string") return new Error(e);
  try {
    return new Error(JSON.stringify(e));
  } catch {
    return new Error(String(e));
  }
}

try {
  JSON.parse("not json");
} catch (e) {
  const err = toError(e);
  console.log("normalised:", err.message);
}

Output:

arduino
normalised: Unexpected token o in JSON at position 1

Recipe 2 — Exhaustive switch with never

Pair a discriminated union with a never-typed default branch so adding a new variant becomes a compile error — closing the TS2366 ("not all paths return") class of bugs at the source.

typescript
type Status = "draft" | "published" | "archived";

function color(s: Status): string {
  switch (s) {
    case "draft":     return "#888";
    case "published": return "#0a0";
    case "archived":  return "#a00";
    default: {
      const _exhaustive: never = s;
      return _exhaustive;
    }
  }
}

console.log(color("published"));

Output:

bash
#0a0

Recipe 3 — Strict object literal type with Except

Using Except from type-fest upgrades Omit's silent typo behaviour into a compile error — surfacing TS2741-equivalent issues earlier.

typescript
import type { Except } from "type-fest";

interface User { id: number; name: string; email: string }

type CreateUserBody = Except<User, "id">;
const body: CreateUserBody = { name: "Alice", email: "alice@example.com" }; // OK
// const bad: Except<User, "emial"> = { ... }; // Error — "emial" not a key

Recipe 4 — Type-safe environment variable getter

A getter that returns the right runtime type for each key and rejects typos. Fixes TS7053 at the point of use.

typescript
const env = {
  PORT: 3000,
  HOST: "0.0.0.0",
  DEBUG: false,
} as const;

type EnvKey = keyof typeof env;
type EnvValue<K extends EnvKey> = typeof env[K];

function getEnv<K extends EnvKey>(key: K): EnvValue<K> {
  const raw = process.env[key];
  const fallback = env[key];
  if (raw === undefined) return fallback;
  if (typeof fallback === "number")  return Number(raw) as EnvValue<K>;
  if (typeof fallback === "boolean") return (raw === "true") as EnvValue<K>;
  return raw as EnvValue<K>;
}

const port = getEnv("PORT");
// const bad = getEnv("PORTT"); // Error TS2345
console.log(port);

Output:

yaml
3000

Recipe 5 — JSON.parse safe wrapper with Zod

Turn an unknown from JSON.parse into a fully validated, fully typed value. Fixes TS2571/TS18046 at the boundary instead of sprinkling casts through the codebase.

typescript
import { z } from "zod";

const UserSchema = z.object({ id: z.number(), name: z.string() });
type User = z.infer<typeof UserSchema>;

function parseUser(raw: string): User {
  const parsed: unknown = JSON.parse(raw);
  return UserSchema.parse(parsed); // throws on shape mismatch
}

console.log(parseUser('{ "id": 1, "name": "Alice" }'));

Output:

bash
{ id: 1, name: 'Alice' }

Recipe 6 — in-operator narrowing for TS2339

When you don't control the input shape, in narrows a union before property access — TypeScript's recommended replacement for blanket casts.

typescript
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape): number {
  if ("radius" in s) return Math.PI * s.radius ** 2;
  return s.side ** 2;
}

console.log(area({ kind: "circle", radius: 3 }));
console.log(area({ kind: "square", side: 4 }));

Output:

code
28.274333882308138
16

Recipe 7 — Typed fetch wrapper using Awaited

A wrapper that infers the JSON response shape from a Zod schema, eliminating both TS2571 (unknown) and TS2339 (property doesn't exist) at the call site.

typescript
import { z } from "zod";

async function getJson<S extends z.ZodTypeAny>(
  url: string,
  schema: S
): Promise<z.infer<S>> {
  const res = await fetch(url);
  const raw: unknown = await res.json();
  return schema.parse(raw);
}

const UserSchema = z.object({ id: z.number(), name: z.string() });

const user = await getJson("/users/1", UserSchema);
console.log(user.name); // typed as string

Recipe 8 — CI gate: tsc --noEmit plus tags

A repeatable CI step that gates on type correctness and tags the failure category so reviewers know whether the issue is a real type bug or a missing declaration file.

bash
npx tsc --noEmit --pretty false 2>&1 | tee tsc.log
grep -E "TS2(304|307)" tsc.log && echo "::warning::Missing types"
grep -E "TS2(322|339|345|740|741)" tsc.log && echo "::warning::Real type bug"
exit ${PIPESTATUS[0]}

Output:

bash
src/api.ts:12:5 - error TS2339: Property 'userId' does not exist on type 'Request'.
::warning::Real type bug