1. Introduction
tsconfig.json is the configuration file that tells the TypeScript compiler (tsc) how to compile a project: which files to include, what JavaScript version and module format to output, how strict type checking should be, and where to place compiled files. Placing a tsconfig.json at the root of a directory also marks that directory as the root of a TypeScript project, which editors like VS Code use to provide accurate IntelliSense and error checking.
Cricket analogy: A ground's pitch report tells the groundskeeper how to prepare the surface, which format to support, and where results get recorded, just as tsconfig.json tells tsc which files to compile, what JS version and module format to output, and where to place results.
A well-tuned tsconfig.json is one of the biggest levers for both type safety and build performance in a real project. Running tsc --init scaffolds a starter file with common options commented out, and most production projects then adjust strictness, target/module settings, and file inclusion rules to match their runtime environment (browser, Node.js, bundler).
Cricket analogy: Just as tuning net-practice intensity based on whether the next match is T20 or a Test is one of the biggest levers for a team's performance, tuning tsconfig.json's strictness, target, and module settings to match the runtime (browser, Node.js, bundler) is one of the biggest levers for a project's safety and build performance.
2. Syntax
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}3. Explanation
tsconfig.json controls compiler behavior through the top-level compilerOptions object plus include/exclude (and optionally files), which control which files are compiled. Among compilerOptions, key options like strict, target, module, outDir, noImplicitAny, and strictNullChecks significantly affect type safety: target sets the ECMAScript version of emitted JavaScript (affecting which syntax/APIs are downleveled), module sets the emitted module system (CommonJS, ESNext, etc.), and outDir/rootDir control where compiled output goes and how the source tree maps to it.
Cricket analogy: The compilerOptions object is like a coach's master playbook, while include/exclude are like the squad selection list; within it, target decides which era's rules apply (helmet regulations, DRS availability), and outDir decides which stadium the final match gets played in.
strict (bundles multiple strict checks) is a single flag that enables a whole family of stricter type-checking flags at once — including noImplicitAny (disallows variables/parameters implicitly typed as any), strictNullChecks (treats null/undefined as distinct types that must be explicitly handled), strictFunctionTypes, strictBindCallApply, and several others. Turning on strict: true is the single most impactful setting for catching real bugs, and it is the recommended default for all new TypeScript projects. include/exclude control which files are compiled: include lists glob patterns of files to bring into the program, while exclude removes matching files (commonly node_modules, build output, and test files) even if they'd otherwise match include.
Cricket analogy: Turning on strict: true is like a captain enforcing full fielding-drill discipline at once, no dropped catches (noImplicitAny), no ignoring a batter's null run-out call (strictNullChecks), while include/exclude are the squad list that keeps injured or reserve players (node_modules, test files) out of the matchday XI.
extends lets one tsconfig.json inherit settings from another (e.g., a shared tsconfig.base.json across a monorepo's packages), and references combined with "composite": true enables TypeScript's project references feature for incrementally building multi-package repositories faster.
Gotcha: exclude only affects which files TypeScript directly compiles as part of the program — it does NOT stop a file from being type-checked if it's still reachable via an import from an included file. Excluding node_modules doesn't prevent type errors from a broken @types package if something in src imports it; and forgetting to enable strict (or individually enabling noImplicitAny/strictNullChecks) silently allows any-typed values and unchecked null/undefined access to slip through, which is one of the most common causes of runtime TypeErrors in supposedly type-safe code.
4. Example
// tsconfig.json for a Node.js CLI tool
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
// src/greet.ts
export function greet(name: string | null): string {
if (name === null) {
return "Hello, stranger!";
}
return `Hello, ${name}!`;
}5. Output
Running `tsc` with the config above compiles src/greet.ts to dist/greet.js targeting ES2022 syntax with CommonJS module.exports.
With "strictNullChecks": true, calling greet(undefined) is a COMPILE-TIME ERROR:
Argument of type 'undefined' is not assignable to parameter of type 'string | null'.
Without strictNullChecks (strict: false), the same call would compile without error and risk a runtime issue if `name` were used unsafely elsewhere without a null check.
Removing `src/greet.ts` from the include pattern (or adding it to exclude) would make `tsc` skip it, and any file importing it would then fail to resolve the module during compilation.6. Key Takeaways
- tsconfig.json controls compiler behavior via
compilerOptionsplusinclude/excludeto determine which files are compiled. strict: truebundles multiple checks (noImplicitAny, strictNullChecks, and more) and is the recommended default for new projects.targetsets the emitted ECMAScript version;modulesets the emitted module system (CommonJS, ESNext, etc.).outDir/rootDircontrol where compiled output goes and how the source tree structure is mirrored there.excludeprevents direct compilation of matching files but does not block type-checking of files still reachable via imports.extendsand project references (composite/references) support sharing config and speeding up builds across monorepo packages.
Practice what you learned
1. What does enabling `"strict": true` in tsconfig.json actually do?
2. Which tsconfig.json option determines the ECMAScript version of the emitted JavaScript?
3. Why doesn't `exclude` fully guarantee that a file is never type-checked?
4. What is the effect of `strictNullChecks` specifically?
5. What does the `outDir` compiler option control?
Was this page helpful?
You May Also Like
Modules in TypeScript
How TypeScript uses ES module syntax (import/export) to organize code across files, with type-only imports and module resolution basics.
Declaration Files (.d.ts) in TypeScript
How .d.ts files describe the shape of JavaScript code for the TypeScript compiler, including DefinitelyTyped (@types) packages.
Namespaces in TypeScript
TypeScript's legacy 'internal modules' feature for grouping related code under a single global name, and why ES modules are now preferred.
any, unknown, never and void in TypeScript
The four special types that sit outside normal type checking — the unsafe escape hatch, the safe escape hatch, the impossible type, and the absent-return type.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics