Skip to content

TypeScript tsconfig Deep Dive — Complete Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about TypeScript tsconfig Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.

The tsconfig.json file is the control center of every TypeScript project — it configures compilation targets, module systems, strictness levels, output directories, and dozens of options that determine how the TypeScript compiler processes your code.

What You'll Learn

  • Every strict mode flag explained
  • target vs module differences
  • Module resolution strategies
  • Source maps, declarations, and output control
  • Recommended configurations for different project types

Why It Matters

The wrong tsconfig can silently hide real type errors (noImplicitAny off), produce bloated output (target ES5 unnecessarily), or fail to compile on other machines (missing moduleResolution). Understanding tsconfig is essential for every TypeScript developer.

Real-World Use

DodaTech's standard tsconfig template is reviewed quarterly across all projects. A misconfigured strict: false once let a null-reference bug slip into the Doda Browser extension, causing crashes on Firefox. Now strict mode is non-negotiable.

Learning Path

flowchart LR
  A[Branded Types] --> B[tsconfig Deep Dive]
  B --> C[Project References]
  B --> D[You Are Here]
  C --> E[Module Resolution]
  E --> F[Source Maps]

The strict Family

Enabling strict: true turns on all these checks:

strictNullChecks

When accessing a value that could be null or undefined, TypeScript errors:

// With strictNullChecks: true
function getLength(s: string | null): number {
  // return s.length;    // Error: Object is possibly 'null'
  if (s === null) return 0;
  return s.length;       // OK — narrowed
}

noImplicitAny

Errors when TypeScript cannot infer a type and defaults to any:

// With noImplicitAny: true
function process(value) { } // Error: Parameter 'value' implicitly has 'any' type

strictFunctionTypes

Makes function parameter types contravariant (sound):

// With strictFunctionTypes: true
type Fn1 = (x: string | number) => void;
type Fn2 = (x: string) => void;
let f1: Fn1 = (x) => {};
let f2: Fn2 = (x) => {};
// f1 = f2; // Error: not assignable

strictBindCallApply

Types .bind, .call, and .apply correctly:

function greet(name: string, age: number): string {
  return `${name} is ${age}`;
}
const bound = greet.bind(null, "Alice"); // type: (age: number) => string

strictPropertyInitialization

Ensures class properties are initialized:

class User {
  name: string; // Error: not initialized
  // Fix: name: string = ""; or name!: string (definite assignment)
}

noImplicitThis

Errors when this is used without a type:

function onClick() {
  console.log(this); // Error: 'this' implicitly has type 'any'
}

alwaysStrict

Adds "use strict" to all output files.

target

Controls which ECMAScript features are allowed in source and how features are downleveled:

{
  "compilerOptions": {
    "target": "ES2022"
  }
}
target Features Allowed Output Style
ES5 Limited Heavy polyfilling
ES2015 classes, arrow functions Moderate
ES2020 optional chaining, nullish coalescing Minimal
ES2022 class fields, top-level await Almost none
ESNext Latest proposals No downleveling

Recommendation: Use ES2022 for modern Node.js or browser targets. Use ESNext if you use a bundler.

module

Controls the module system in output:

{
  "compilerOptions": {
    "module": "ESNext"
  }
}
module Output Format Use When
CommonJS require/module.exports Node.js without ESM
ESNext import/export Bundlers (Webpack, Vite)
Node16 Mixed CJS/ESM Node.js 16+ with type: module
NodeNext Like Node16 Latest Node.js
UMD Universal Library distribution

Module Resolution

{
  "compilerOptions": {
    "moduleResolution": "bundler"
  }
}
Resolution Use Case
classic Legacy (avoid)
node Node.js CJS
node16 Node.js ESM/CJS
nodenext Latest Node.js
bundler Vite, Webpack, Esbuild

Paths and Base URL

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}

Now you can import: import { foo } from "@/utils/helpers" instead of relative paths.

Output Control

{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "removeComments": true
  }
}
Option Purpose
outDir Where compiled JS goes
rootDir Where TS source is
declaration Generate .d.ts files
declarationMap Map .d.ts to .ts sources
sourceMap Debug TS directly
removeComments Strip comments from output

Common Mistakes

1. Not Enabling strict Mode

strict: true catches more bugs. There is almost no reason to disable it.

2. Confusing target and module

target controls language features. module controls module format. They are independent.

3. Missing skipLibCheck

Without it, TypeScript checks all @types/* packages, slowing compilation.

4. Not Using noUnusedLocals and noUnusedParameters

These catch dead code:

{ "noUnusedLocals": true, "noUnusedParameters": true }

5. Wrong moduleResolution for the Environment

Using node resolution with a bundler causes issues. Use bundler.

Practice Questions

  1. What does strict: true enable? strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, alwaysStrict.

  2. What is the difference between target and module? target controls language features (async/await, arrows). module controls module format (CommonJS, ESM).

  3. What does skipLibCheck do? Skips Type Checking of .d.ts files in node_modules, speeding up compilation.

  4. Why use baseUrl and paths? To create clean import aliases instead of deep relative paths.

Challenge: Starting from tsc --init, modify the generated tsconfig to target a Node.js 20 project using ESM, with strict mode, source maps, and @/ path aliases.

FAQ

Should I use `strict: true` for all projects?

Yes. Strict mode catches real bugs and improves code quality. The initial effort to fix existing errors is worth it.

What is `isolatedModules`?

Ensures each file can be transpiled in isolation (required by some bundlers like Babel and esbuild).

What is `esModuleInterop`?

Helps compatibility between CommonJS and ES modules, allowing import React from "react" instead of import * as React.

What is `forceConsistentCasingInFileNames`?

Ensures file import paths use consistent casing across all files, preventing case-sensitive filesystem issues.

What is the recommended tsconfig for a library?

Enable declaration: true and declarationMap: true so consumers get type information.

Node.js Library

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"]
}

React Application

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "jsx": "react-jsx",
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

What's Next

Now explore project references for multi-project setups:

Lesson Description
{{< ref "/programming-languages/typescript/30-branded-types" >}} Review branded types
{{< ref "/programming-languages/typescript/32-project-references" >}} Composite projects, references, build mode
{{< ref "/programming-languages/typescript/33-module-resolution" >}} Classic vs node, paths, baseUrl

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro