On this page · sections
Compiler & TypeScript Configuration
tsconfig.json is the contract between your codebase and the TypeScript compiler. Senior engineers know which flags prevent production bugs, how module resolution affects imports, and when project references split a monorepo into fast, incremental builds.
| Area | Key options | Impact |
|---|---|---|
| Strictness | strict, strictNullChecks, noImplicitAny | Null safety, no silent any |
| Modules | module, moduleResolution, paths | Import behavior, bundler vs Node |
| Output | target, lib, outDir, declaration | JS version, DOM APIs, publish types |
| DX / CI | skipLibCheck, incremental, composite | Build speed, project references |
The tsconfig.json structure
Definition
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"isolatedModules": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}include/exclude— which files enter the compilation.extends— inherit from a shared config ("extends": "@repo/tsconfig/base.json").
Interview: Next.js and Vite projects often set
noEmit: truebecause the bundler emits JS; TypeScript only type-checks.
Strict mode
Definition
"strict": true enables a family of compiler checks that catch the most common production bugs:
| Flag | What it catches |
|---|---|
strictNullChecks | null / undefined used where a value is required |
noImplicitAny | Parameters/locals inferred as any |
strictFunctionTypes | Unsafe function parameter contravariance |
strictBindCallApply | Wrong this types on bind / call / apply |
strictPropertyInitialization | Uninitialized class fields |
noImplicitThis | this with implicit any |
alwaysStrict | Emit "use strict" in JS output |
useUnknownInCatchVariables | catch (e) is unknown, not any |
{
"compilerOptions": {
"strict": true
}
}Enable flags individually when migrating legacy code:
{
"compilerOptions": {
"strict": false,
"strictNullChecks": true,
"noImplicitAny": true
}
}strictNullChecks and noImplicitAny
strictNullChecks forces explicit handling of null/undefined; noImplicitAny bans silent any inference.
function getLength(s: string | null) {
if (s === null) return 0;
return s.length;
}
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};Interview:
strictNullChecksis the single highest-impact strict flag for frontend apps.
strictFunctionTypes and useUnknownInCatchVariables
Function contravariance
type Handler = (event: MouseEvent | KeyboardEvent) => void;
const onKey: (event: KeyboardEvent) => void = (e) => {
console.log(e.key);
};
// ❌ strictFunctionTypes — Handler expects broader input
const handler: Handler = onKey;Catch variables as unknown
try {
await fetchUser();
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error("Unknown error", error);
}
}Interview: Treat caught values as
unknownand narrow — same discipline as API boundaries.
Module resolution
moduleResolution options
| Value | Use case |
|---|---|
bundler | Vite, Webpack, esbuild — allows extensionless imports |
node16 / nodenext | Node ESM/CJS with package.json "exports" |
classic | Legacy — avoid in new projects |
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
}
}paths aliases
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@ui/*": ["packages/ui/src/*"]
}
}
}TypeScript resolves @/components/Button to src/components/Button. Your bundler must mirror the same aliases in vite.config.ts or next.config.js.
package.json exports (Node libraries)
{
"name": "@acme/utils",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}Use "moduleResolution": "nodenext" when publishing packages consumed by Node.
target, lib, and build performance
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"skipLibCheck": true,
"incremental": true
}
}target/lib— JS syntax level and built-in APIs (DOMfordocument,fetch).skipLibCheck— skip.d.tsinnode_modules(major CI speed win).- App projects:
noEmit: true(bundler emits JS). Libraries:declaration: true.
Project references
Definition
Split a monorepo into composite projects that build independently and in dependency order:
// packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "dist"
}
}
// apps/web/tsconfig.json
{
"references": [{ "path": "../../packages/shared" }]
}Build with tsc -b — only rebuilds changed projects. Requires composite: true + declaration: true on referenced packages.
Interview: Project references give incremental monorepo builds.
Migrating to strict
Practical approach
- Enable
strictNullChecksandnoImplicitAnyfirst — highest ROI. - Fix errors module by module (feature folders, not random files).
- Use
@ts-expect-errorsparingly with a ticket link; don’t blanket-disable strict. - Add
strict: trueto CI once error count is manageable.
{
"compilerOptions": {
"strict": true,
"skipLibCheck": true
}
}Senior Interview Summary
What to say
Strict mode bundles null safety, no implicit
any, and safer function types.moduleResolution: bundlerfits Vite/Next;nodenextfits published Node packages. Project references enable incremental monorepo builds.skipLibCheckspeeds CI without weakening your app types.
The five flags interviewers ask about most
strictNullChecks → null/undefined must be handled
noImplicitAny → no silent any inference
strictFunctionTypes → safer callback typing
strictPropertyInitialization → class fields initialized
useUnknownInCatchVariables → catch (e) is unknownPractical rule
New project → strict: true, skipLibCheck: true, isolatedModules: true
Next/Vite app → noEmit: true, moduleResolution: bundler
Published library → declaration: true, nodenext resolution
Monorepo → composite + project references + shared base config