Error Handling and Debugging Techniques

1. TypeScript Compiler Error Messages

Error Code Message Pattern Common Cause Solution
TS2322 Type 'X' is not assignable to type 'Y' Type mismatch in assignment Check types, add type assertion, or fix source type
TS2345 Argument of type 'X' is not assignable to parameter of type 'Y' Wrong function argument type Pass correct type or update function signature
TS2339 Property 'X' does not exist on type 'Y' Accessing non-existent property Add property to type, use optional chaining, or type guard
TS2571 Object is of type 'unknown' Using unknown type without narrowing Add type guard or type assertion
TS2769 No overload matches this call Function call doesn't match any signature Check argument types and count
TS2740 Type 'X' is missing properties from type 'Y' Incomplete object initialization Add missing properties or use Partial<T>
TS7006 Parameter 'X' implicitly has an 'any' type Missing type annotation with noImplicitAny Add explicit type annotation
TS2304 Cannot find name 'X' Undefined variable/type, missing import Import symbol or install @types package

Example: Common compiler errors and fixes

// TS2322 - Type mismatch
// ❌ Error
const num: number = '42';  // Type 'string' is not assignable to type 'number'
// ✅ Fix
const num: number = 42;

// TS2345 - Wrong argument type
// ❌ Error
function greet(name: string) { }
greet(42);  // Argument of type 'number' is not assignable to parameter of type 'string'
// ✅ Fix
greet('Alice');

// TS2339 - Property doesn't exist
// ❌ Error
interface User { name: string; }
const user: User = { name: 'Alice' };
console.log(user.age);  // Property 'age' does not exist on type 'User'
// ✅ Fix 1 - Add to interface
interface User { name: string; age?: number; }
// ✅ Fix 2 - Type guard
if ('age' in user) {
  console.log(user.age);
}

// TS2571 - Unknown type
// ❌ Error
function process(data: unknown) {
  console.log(data.name);  // Object is of type 'unknown'
}
// ✅ Fix - Type guard
function process(data: unknown) {
  if (typeof data === 'object' && data !== null && 'name' in data) {
    console.log((data as { name: string }).name);
  }
}

// TS2769 - No matching overload
// ❌ Error
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any) { return a + b; }
add(1, '2');  // No overload matches this call
// ✅ Fix - Use compatible types
add(1, 2);  // OK
add('1', '2');  // OK

// TS2740 - Missing properties
// ❌ Error
interface Config {
  host: string;
  port: number;
  secure: boolean;
}
const config: Config = { host: 'localhost' };  // Missing 'port' and 'secure'
// ✅ Fix - Provide all properties
const config: Config = { host: 'localhost', port: 3000, secure: true };

// TS7006 - Implicit any
// ❌ Error (with noImplicitAny)
function process(data) {  // Parameter 'data' implicitly has an 'any' type
  console.log(data);
}
// ✅ Fix - Add type annotation
function process(data: unknown) {
  console.log(data);
}

Example: Understanding error messages

// Complex error message breakdown

// Error:
// Type '{ name: string; age: number; }' is not assignable to type 'User'.
//   Property 'email' is missing in type '{ name: string; age: number; }' 
//   but required in type 'User'.

interface User {
  name: string;
  age: number;
  email: string;  // Required property
}

const user: User = {  // ❌ Error
  name: 'Alice',
  age: 30
  // Missing: email
};

// Reading the error:
// 1. Main message: type mismatch
// 2. Specific issue: missing 'email' property
// 3. Location: 'email' is required in User interface

// Error with complex type
type Handler<T> = (data: T) => void;

const handler: Handler<string> = (data: number) => {  // ❌ Error
  console.log(data);
};

// Error: Type '(data: number) => void' is not assignable to type 'Handler<string>'.
//   Types of parameters 'data' and 'data' are incompatible.
//     Type 'string' is not assignable to type 'number'.

// Understanding:
// 1. Handler expects string parameter
// 2. Function defined with number parameter
// 3. Parameter types are incompatible

// Use --explainFiles flag for more details
tsc --explainFiles

// Use --noErrorTruncation for full error messages
tsc --noErrorTruncation

2. Type Error Diagnosis and Resolution

Technique Method When to Use Benefit
Hover for Type Info Hover over symbol in IDE Understand inferred types, signatures See what TypeScript thinks the type is
Go to Definition F12 on symbol Find type definitions, interfaces Understand structure and constraints
Type Annotations Add explicit types to narrow inference Disambiguate complex types Control type inference, catch errors early
@ts-expect-error Suppress error, mark as intentional Known type issue, waiting for fix Document known limitations
@ts-ignore Suppress next line error (avoid if possible) Last resort for unfixable issues Bypass type checker (dangerous)
Type Assertion Use as Type to override inference You know more than compiler Tell compiler the actual type
Isolate Problem Extract to separate file/function Complex type errors Reduce complexity, test in isolation
--noEmit Type check without generating output Fast error checking Quick validation in CI/CD

Example: Diagnosis techniques

// 1. Use type annotations to narrow inference
// Problem: Generic type too broad
const data = [];  // Inferred as: never[]
data.push(1);  // ❌ Error

// Solution: Add type annotation
const data: number[] = [];
data.push(1);  // ✅ OK

// 2. Break down complex types
// Problem: Complex union causing issues
type ComplexType = 
  | { type: 'a'; value: string }
  | { type: 'b'; value: number }
  | { type: 'c'; value: boolean };

function handle(data: ComplexType) {
  // Hard to debug all at once
}

// Solution: Test each branch separately
function handleA(data: Extract<ComplexType, { type: 'a' }>) {
  console.log(data.value.toUpperCase());  // string methods available
}

// 3. Use @ts-expect-error for known issues
// Third-party library has incorrect types
// @ts-expect-error - Library types are wrong, will be fixed in v2.0
const result = someLibraryFunction('param');

// 4. Type assertions when you know better than compiler
const canvas = document.getElementById('canvas');  // HTMLElement | null
// You know it's a canvas element
const ctx = (canvas as HTMLCanvasElement).getContext('2d');

// 5. Diagnostic types to inspect complex types
type Inspect<T> = { [K in keyof T]: T[K] };

type Complex = Partial<Omit<User, 'id'>> & { active: boolean };
type Simplified = Inspect<Complex>;  // Hover to see resolved type

// 6. Check assignability with conditional types
type IsAssignable<T, U> = T extends U ? true : false;

type Test1 = IsAssignable<string, string | number>;  // true
type Test2 = IsAssignable<number, string>;  // false

// 7. Use --traceResolution to debug module resolution
// tsc --traceResolution | grep "my-module"

// 8. Enable verbose errors in tsconfig
{
  "compilerOptions": {
    "noErrorTruncation": true  // Show full error messages
  }
}

Example: Debugging type inference

// Helper type to see inferred types
type Debug<T> = { [K in keyof T]: T[K] } & {};

// Example: Understanding generic inference
function map<T, U>(array: T[], fn: (item: T) => U): U[] {
  return array.map(fn);
}

// Hover over each to see inferred types
const numbers = [1, 2, 3];
const strings = map(numbers, n => n.toString());
// T = number, U = string, result = string[]

// Use satisfies to validate without changing type
const config = {
  host: 'localhost',
  port: 3000
} satisfies Record<string, string | number>;

config.host;  // Still inferred as 'localhost' literal type

// Debugging discriminated unions
type Result<T> = 
  | { success: true; data: T }
  | { success: false; error: string };

function unwrap<T>(result: Result<T>): T {
  if (result.success) {
    // Hover over result.data to see narrowing
    return result.data;  // TypeScript knows: { success: true; data: T }
  }
  throw new Error(result.error);  // TypeScript knows: { success: false; error: string }
}

// Debug utility to force type errors (see full type)
type ForceError<T> = T extends never ? T : never;

// Uncomment to see full type structure
// type DebugMyType = ForceError<ComplexType>;

// Check if types match
type Equals<X, Y> = 
  (<T>() => T extends X ? 1 : 2) extends 
  (<T>() => T extends Y ? 1 : 2) ? true : false;

type Test = Equals<{ a: number }, { a: number }>;  // true

3. Source Map Configuration and Debugging

Option Configuration Purpose Use Case
sourceMap "sourceMap": true Generate .js.map files - map compiled to source Debug TypeScript in browser/Node.js
inlineSourceMap "inlineSourceMap": true Embed source maps in .js files Single-file distribution
inlineSources "inlineSources": true Include TypeScript source in map No separate .ts files needed for debugging
declarationMap "declarationMap": true Generate .d.ts.map for declaration files Go to source from .d.ts files
sourceRoot "sourceRoot": "./src" Specify root for source files in maps Correct source paths in debugger
mapRoot "mapRoot": "./maps" Specify location of map files Separate map file deployment
VS Code Debugging launch.json configuration Debug TypeScript directly in VS Code Breakpoints in .ts files

Example: Source map configuration

// tsconfig.json for debugging
{
  "compilerOptions": {
    // Source map generation
    "sourceMap": true,           // Generate .js.map files
    "declarationMap": true,      // Generate .d.ts.map files
    
    // Inline options (for bundlers)
    "inlineSourceMap": false,    // Don't inline (separate files)
    "inlineSources": false,      // Don't embed TypeScript source
    
    // Source paths
    "sourceRoot": "",            // Relative to source map
    "mapRoot": "",               // Where to find maps
    
    // Output settings
    "outDir": "./dist",
    "rootDir": "./src",
    
    // Keep for better debugging
    "removeComments": false,
    "preserveConstEnums": true
  }
}

// For production (smaller bundles)
{
  "compilerOptions": {
    "sourceMap": false,          // No source maps
    "removeComments": true,      // Strip comments
    "declaration": false         // No .d.ts files
  }
}

// For development (best debugging)
{
  "compilerOptions": {
    "sourceMap": true,
    "declarationMap": true,
    "inlineSources": true,       // Embed sources (easier debugging)
    "removeComments": false
  }
}

// Webpack source map options
module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',  // Best for development
  // devtool: 'source-map',      // Separate files (production)
  // devtool: 'eval-source-map', // Fast rebuild (development)
  // devtool: 'cheap-source-map', // Faster, less accurate
};

Example: VS Code debugging configuration

// .vscode/launch.json - Node.js debugging
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug TypeScript",
      "program": "${workspaceFolder}/src/index.ts",
      "preLaunchTask": "tsc: build - tsconfig.json",
      "outFiles": ["${workspaceFolder}/dist/**/*.js"],
      "sourceMaps": true,
      "skipFiles": ["<node_internals>/**"],
      "console": "integratedTerminal"
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug with ts-node",
      "runtimeArgs": ["-r", "ts-node/register"],
      "args": ["${workspaceFolder}/src/index.ts"],
      "skipFiles": ["<node_internals>/**"],
      "console": "integratedTerminal"
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Jest Tests",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["--runInBand", "--no-cache"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

// .vscode/launch.json - Chrome debugging (React, etc.)
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "Launch Chrome",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceFolder}/src",
      "sourceMapPathOverrides": {
        "webpack:///src/*": "${webRoot}/*"
      }
    }
  ]
}

// .vscode/tasks.json - Build before debugging
{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "typescript",
      "tsconfig": "tsconfig.json",
      "option": "watch",
      "problemMatcher": ["$tsc-watch"],
      "group": "build",
      "label": "tsc: watch - tsconfig.json"
    }
  ]
}

4. Runtime Error Handling with Type Safety

Pattern Implementation Type Safety Use Case
Result Type Discriminated union for success/failure Force error handling at compile time Avoid throwing exceptions, functional approach
Try-Catch with Typing Type the error with type guards Handle specific error types safely API calls, file operations
Custom Error Classes Extend Error with additional properties Discriminate error types Domain-specific errors
Error Boundary Pattern Wrap risky operations with error handling Isolate errors, prevent propagation React error boundaries, middleware
Validation Libraries Zod, io-ts, Yup for runtime validation Runtime and compile-time safety API responses, user input
Never Type Functions that never return (throw/exit) Document unreachable code Error handlers, infinite loops
Option/Maybe Type Represent nullable values explicitly Force null checking Functional programming patterns

Example: Result type pattern

// Result type for error handling without exceptions
type Result<T, E = Error> = 
  | { success: true; value: T }
  | { success: false; error: E };

// Usage
function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return { success: false, error: 'Division by zero' };
  }
  return { success: true, value: a / b };
}

// Consumer must handle both cases
const result = divide(10, 2);
if (result.success) {
  console.log(result.value);  // TypeScript knows: value exists
} else {
  console.error(result.error);  // TypeScript knows: error exists
}

// Helper functions
function ok<T>(value: T): Result<T, never> {
  return { success: true, value };
}

function err<E>(error: E): Result<never, E> {
  return { success: false, error };
}

// Async version
async function fetchUser(id: string): Promise<Result<User, string>> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return err(`Failed to fetch user: ${response.status}`);
    }
    const user = await response.json();
    return ok(user);
  } catch (error) {
    return err(`Network error: ${error}`);
  }
}

// Chain results
function map<T, U, E>(
  result: Result<T, E>,
  fn: (value: T) => U
): Result<U, E> {
  return result.success ? ok(fn(result.value)) : result;
}

const doubled = map(divide(10, 2), n => n * 2);

Example: Custom error classes and type guards

// Custom error hierarchy
class AppError extends Error {
  constructor(message: string) {
    super(message);
    this.name = this.constructor.name;
    Object.setPrototypeOf(this, new.target.prototype);
  }
}

class ValidationError extends AppError {
  constructor(
    message: string,
    public field: string,
    public value: unknown
  ) {
    super(message);
  }
}

class NetworkError extends AppError {
  constructor(
    message: string,
    public statusCode: number,
    public url: string
  ) {
    super(message);
  }
}

class NotFoundError extends AppError {
  constructor(
    public resource: string,
    public id: string
  ) {
    super(`${resource} with id ${id} not found`);
  }
}

// Type guards for error handling
function isValidationError(error: unknown): error is ValidationError {
  return error instanceof ValidationError;
}

function isNetworkError(error: unknown): error is NetworkError {
  return error instanceof NetworkError;
}

function isNotFoundError(error: unknown): error is NotFoundError {
  return error instanceof NotFoundError;
}

// Usage with proper typing
async function getUserData(id: string): Promise<User> {
  try {
    const response = await fetch(`/api/users/${id}`);
    
    if (!response.ok) {
      if (response.status === 404) {
        throw new NotFoundError('User', id);
      }
      throw new NetworkError(
        'Failed to fetch user',
        response.status,
        response.url
      );
    }
    
    const data = await response.json();
    
    if (!data.email) {
      throw new ValidationError(
        'Email is required',
        'email',
        data.email
      );
    }
    
    return data;
    
  } catch (error) {
    // Type-safe error handling
    if (isValidationError(error)) {
      console.error(`Validation failed for ${error.field}:`, error.value);
    } else if (isNetworkError(error)) {
      console.error(`Network error ${error.statusCode} at ${error.url}`);
    } else if (isNotFoundError(error)) {
      console.error(`${error.resource} not found: ${error.id}`);
    } else if (error instanceof Error) {
      console.error('Unexpected error:', error.message);
    } else {
      console.error('Unknown error:', error);
    }
    throw error;
  }
}

// Generic error handler
function handleError(error: unknown): never {
  if (error instanceof AppError) {
    console.error(`[${error.name}] ${error.message}`);
  } else if (error instanceof Error) {
    console.error(error.message);
  } else {
    console.error('Unknown error:', error);
  }
  process.exit(1);
}

5. Type-only Imports for Debugging

Feature Syntax Purpose Benefit
Type-only Import import type { T } from './module' Import only for type checking - removed at runtime No runtime overhead, clear intent
Inline Type Import import { type T, value } from './module' Mix type and value imports Cleaner syntax, single import statement
Type-only Export export type { T } from './module' Re-export only types Clear API boundaries
importsNotUsedAsValues "verbatimModuleSyntax": true Preserve or remove unused imports Smaller bundles, clearer semantics
isolatedModules "isolatedModules": true Ensure each file can transpile independently Required for esbuild, SWC
preserveValueImports "preserveValueImports": true Keep imports even if only for types Side-effect preservation

Example: Type-only imports

// types.ts
export interface User {
  id: string;
  name: string;
}

export class UserService {
  getUser(id: string): User {
    // ...
  }
}

export const API_URL = 'https://api.example.com';

// main.ts - Different import styles

// 1. Type-only import (TS 3.8+)
import type { User } from './types';
// Removed at runtime, only for type checking

// 2. Mixed import (TS 4.5+ inline style)
import { type User, UserService, API_URL } from './types';
// User is type-only, UserService and API_URL are values

// 3. Separate imports (clear separation)
import type { User } from './types';
import { UserService, API_URL } from './types';

// 4. Type-only export
// api.ts
export type { User } from './types';  // Re-export only type
export { UserService } from './types';  // Re-export value

// Benefits:
// - Smaller bundles (types removed)
// - Clear intent (type vs value)
// - Better tree shaking
// - Required for isolatedModules

// tsconfig.json
{
  "compilerOptions": {
    // Enforce type-only imports where possible
    "verbatimModuleSyntax": true,  // TS 5.0+ (replaces below)
    
    // Or legacy options:
    "importsNotUsedAsValues": "error",
    "preserveValueImports": false,
    "isolatedModules": true
  }
}

// ESLint rule to enforce
{
  "rules": {
    "@typescript-eslint/consistent-type-imports": ["error", {
      "prefer": "type-imports",
      "fixStyle": "inline-type-imports"
    }]
  }
}

Example: Debugging with type imports

// When debugging, type-only imports don't affect runtime

// Before optimization
import { User, ApiClient, ValidationError } from './library';

// User is only used for types
function processUser(user: User) { }

// After optimization
import type { User } from './library';  // Type-only
import { ApiClient, ValidationError } from './library';  // Runtime values

function processUser(user: User) { }  // User type available at compile time

// Helps identify dead code
// If you see a regular import only used for types,
// convert it to type-only import

// Example: Debugging circular dependencies
// file-a.ts
import type { TypeB } from './file-b';  // Safe - type-only
export interface TypeA {
  b: TypeB;
}

// file-b.ts
import type { TypeA } from './file-a';  // Safe - type-only
export interface TypeB {
  a: TypeA;
}

// Type-only imports don't cause runtime circular dependency

// Debugging bundle size
// Before:
import { LargeLibrary } from 'some-package';
type Config = LargeLibrary.Config;  // Imports entire library!

// After:
import type { Config } from 'some-package';  // Only type, no runtime import

// Use source-map-explorer to verify:
npm run build
source-map-explorer dist/*.js

6. Conditional Compilation and Environment Types

Technique Implementation Purpose Use Case
Environment Variables process.env.NODE_ENV with types Different behavior per environment Development vs production features
Type Declarations Declare global types for env variables Type safety for process.env Avoid runtime undefined errors
Const Assertions const config = { ... } as const Narrow types to literal values Type-safe configuration
Dead Code Elimination Bundler removes unreachable code Smaller production bundles Debug logging, dev tools
Multiple tsconfig Different configs per environment Different types/settings Test vs production builds
Conditional Types Types based on environment Different APIs per environment Mock vs real implementations
Feature Flags Type-safe feature toggles Enable/disable features safely A/B testing, gradual rollouts

Example: Environment-specific types

// env.d.ts - Type environment variables
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' | 'production' | 'test';
      API_URL: string;
      DEBUG?: 'true' | 'false';
      DATABASE_URL: string;
      PORT?: string;
    }
  }
}

export {};

// Now process.env is fully typed
const apiUrl: string = process.env.API_URL;  // Type-safe
const port: string = process.env.PORT ?? '3000';

// Conditional compilation with dead code elimination
const isDevelopment = process.env.NODE_ENV === 'development';

if (isDevelopment) {
  // This code is removed in production builds by bundlers
  console.log('Debug mode enabled');
  enableDevTools();
}

// Better: Use function for tree-shaking
function debug(message: string) {
  if (process.env.NODE_ENV === 'development') {
    console.log('[DEBUG]', message);
  }
}

// Even better: Replace at build time
// webpack.DefinePlugin or Vite define
const DEBUG = __DEV__;  // Replaced with true/false at build time

if (DEBUG) {
  console.log('Development mode');
}

// Type-safe config based on environment
type Config<T extends 'development' | 'production'> = T extends 'development'
  ? { debug: true; logLevel: 'verbose'; apiUrl: string }
  : { debug: false; logLevel: 'error'; apiUrl: string; cdnUrl: string };

function getConfig<T extends 'development' | 'production'>(
  env: T
): Config<T> {
  if (env === 'development') {
    return {
      debug: true,
      logLevel: 'verbose',
      apiUrl: 'http://localhost:3000'
    } as Config<T>;
  } else {
    return {
      debug: false,
      logLevel: 'error',
      apiUrl: 'https://api.example.com',
      cdnUrl: 'https://cdn.example.com'
    } as Config<T>;
  }
}

const devConfig = getConfig('development');
devConfig.debug;  // true (type-safe)

Example: Feature flags and conditional types

// Feature flag system with type safety
type FeatureFlags = {
  newUI: boolean;
  betaFeatures: boolean;
  experimentalAPI: boolean;
};

const flags: FeatureFlags = {
  newUI: process.env.ENABLE_NEW_UI === 'true',
  betaFeatures: process.env.NODE_ENV === 'development',
  experimentalAPI: false
};

// Type-safe feature check
function isFeatureEnabled<K extends keyof FeatureFlags>(
  feature: K
): boolean {
  return flags[feature];
}

// Conditional API based on flags
type API<T extends FeatureFlags> = T['experimentalAPI'] extends true
  ? ExperimentalAPI
  : StableAPI;

interface StableAPI {
  fetch(url: string): Promise<Response>;
}

interface ExperimentalAPI extends StableAPI {
  fetchWithCache(url: string): Promise<Response>;
  preload(urls: string[]): Promise<void>;
}

// Multiple tsconfig for different environments
// tsconfig.base.json - Shared config
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true
  }
}

// tsconfig.json - Development
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "sourceMap": true,
    "declaration": true,
    "types": ["node", "jest"]
  },
  "include": ["src/**/*", "test/**/*"]
}

// tsconfig.prod.json - Production
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "sourceMap": false,
    "removeComments": true,
    "declaration": false
  },
  "include": ["src/**/*"],
  "exclude": ["test/**/*", "**/*.test.ts"]
}

// Build scripts
{
  "scripts": {
    "build:dev": "tsc",
    "build:prod": "tsc -p tsconfig.prod.json"
  }
}

// Webpack/Vite define plugin
// vite.config.ts
export default defineConfig({
  define: {
    '__DEV__': JSON.stringify(process.env.NODE_ENV === 'development'),
    '__VERSION__': JSON.stringify(process.env.npm_package_version),
    'process.env.API_URL': JSON.stringify(process.env.API_URL)
  }
});

// globals.d.ts
declare const __DEV__: boolean;
declare const __VERSION__: string;

// Usage - dead code eliminated in production
if (__DEV__) {
  console.log(`Running version ${__VERSION__}`);
  enableDebugTools();
}
Note: Error handling and debugging best practices:
  • Compiler errors - Read messages carefully, use hover for type info, check error codes
  • Diagnosis - Break down complex types, use type annotations, @ts-expect-error for known issues
  • Source maps - Enable for debugging, use declarationMap for libraries, configure VS Code launch.json
  • Runtime errors - Use Result types, custom error classes with type guards, validation libraries
  • Type imports - Use type-only imports for smaller bundles, enable verbatimModuleSyntax
  • Environment - Type process.env, use feature flags, conditional compilation for optimizations

Error Handling and Debugging Summary

  • Compiler errors - Understand common error codes (TS2322, TS2339, TS2571), read full messages
  • Diagnosis - Use hover, go to definition, type annotations, isolation techniques to debug type errors
  • Source maps - Enable sourceMap and declarationMap, configure VS Code for debugging TypeScript
  • Runtime safety - Result types, custom errors with type guards, validation libraries (Zod, io-ts)
  • Type imports - Use import type for compile-time only imports, smaller bundles, clearer intent
  • Environments - Type process.env, use feature flags, multiple tsconfig files, dead code elimination