skip to content

tsconfig & Compilation

You will learn how tsconfig.json actually shapes a TypeScript project: which flags tighten type checking, how the compiler resolves module specifiers and picks an output target, how declaration files are produced and consumed, and how type-checking fits into a real build. Interviewers ask because most TypeScript setup pain — 'cannot find module', 'it compiles locally but not in CI', a published package with no types — traces straight back to a compiler option nobody on the team understood.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

page 2 of 2

In tsconfig.json, what is the difference between the `esModuleInterop` and `allowSyntheticDefaultImports` compiler options?

level: middleimportance: should knowfreq 55%

basics

~20 s

allowSyntheticDefaultImports is type-checking only: it lets you write a default import from a module that declares no default export. esModuleInterop additionally changes CommonJS emit to wrap the required value, and it turns allowSyntheticDefaultImports on for you.

open as a page

What does the TypeScript compiler option `useDefineForClassFields` change about how class fields are emitted, what is its default, and what kind of working code breaks when it becomes true?

level: middleimportance: should knowfreq 34%

basics

~20 s

useDefineForClassFields switches class field emit from plain assignment to Object.defineProperty semantics, matching the ECMAScript standard. It defaults to true when target is ES2022 or higher. Fields declared without an initializer then overwrite inherited values with undefined, and they shadow base-class accessors instead of calling them.

open as a page

With `"target": "es5"` in tsconfig, TypeScript accepts `for (const x of myArray)` but rejects `for (const [k, v] of myMap)` and points you at the `downlevelIteration` option. Why the difference, and what does enabling that flag change in the emitted code?

level: middleimportance: should knowfreq 38%

basics

~20 s

At an ES5 target TypeScript compiles for...of over arrays and strings into a plain index loop, which cannot work for a Map. downlevelIteration makes the compiler emit helpers that call Symbol.iterator instead, so any iterable works — but the runtime must actually provide Symbol.iterator.

open as a page

In TypeScript, what does the exactOptionalPropertyTypes compiler option change about a property declared retries?: number, and how do you keep allowing an explicit undefined?

level: middleimportance: should knowfreq 30%

basics

~20 s

exactOptionalPropertyTypes separates absent from present-and-undefined. With it on, retries?: number means the property may be missing but must hold a real number when present, so assigning { retries: undefined } is an error; declare retries?: number | undefined to allow both.

open as a page

In TypeScript, what does the noImplicitOverride compiler option require, which class members does it apply to, and what bug is the override keyword designed to catch?

level: middleimportance: should knowfreq 28%

basics

~20 s

noImplicitOverride requires the override keyword on any member that redefines a concrete member inherited from a base class, including properties and accessors. It catches renamed or deleted base members, whose overrides would otherwise silently become brand-new members.

open as a page

In TypeScript, what does the noUncheckedIndexedAccess compiler option change about the type of rows[0] for const rows: string[], and which accesses does it deliberately leave alone?

level: middleimportance: should knowfreq 52%

basics

~10 s

With noUncheckedIndexedAccess on, every read through an array index or an index signature gains undefined: rows[0] is string | undefined rather than string. Declared properties, fixed tuple positions and for...of iteration are unaffected.

open as a page

Why does TypeScript's `isolatedModules` flag reject references to an ambient `declare const enum`, and what does a per-file transpiler do with an ordinary `const enum`?

level: middleimportance: should knowfreq 30%

basics

~20 s

A const enum is normally compiled away by inlining its member values, which requires reading the declaring file. An ambient const enum emits no runtime object at all, so a tool that reads one file has nothing to inline and nothing to reference — hence the error.

open as a page

What does TypeScript's `verbatimModuleSyntax` flag change about how imports and exports are emitted, and which older options does it replace?

level: middleimportance: should knowfreq 44%

basics

~10 s

verbatimModuleSyntax replaces import elision with one syntactic rule: anything written with a type modifier is dropped entirely, and everything else is emitted exactly as written. Added in TypeScript 5.0, it supersedes importsNotUsedAsValues and preserveValueImports.

open as a page

In a TypeScript project compiled with strict enabled, what is the static type of `e` in `try { ... } catch (e) { ... }`, and what do you have to do before reading `e.message`?

level: middleimportance: should knowfreq 45%

basics

~20 s

Under strict, the useUnknownInCatchVariables flag types a catch clause variable as unknown rather than any, so reading any property off it is a compile error. You must narrow first — typically with an instanceof Error check — before touching e.message.

open as a page

A TypeScript class compiled with strict reports "Property 'name' has no initializer and is not definitely assigned in the constructor". Which compiler flag produces that error, and what are the legitimate ways to satisfy it?

level: middleimportance: should knowfreq 42%

basics

~20 s

The strictPropertyInitialization flag, enabled by strict and requiring strictNullChecks, produces that error. Satisfy it by giving the field an initializer, assigning it unconditionally in the constructor, or admitting it may be missing by making the type optional or including undefined.

open as a page

A TypeScript monorepo builds with `tsc -b`, yet every CI run recompiles all projects from scratch even though only one package changed. How do you diagnose and fix that?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Run tsc -b --verbose to see why each project is considered out of date. Typically CI restores neither the .tsbuildinfo files nor the emitted outputs, or a fresh checkout resets file timestamps, or a changed compiler version or config invalidates the recorded build state.

open as a page

Your CI step that runs tsc --noEmit has grown to several minutes on a mid-sized TypeScript repo. Which compiler flags and measurements would you use to find where the time is actually going?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Start with tsc --extendedDiagnostics for a breakdown of parse, bind, check and emit time plus file, type and instantiation counts. Then use --generateTrace with @typescript/analyze-trace to find the specific files and types that dominate checking.

open as a page

A TypeScript project has a declaration file containing `declare const BUILD_ID: string;`. Every use of `BUILD_ID` type-checks, yet the deployed app throws "BUILD_ID is not defined" at runtime. What does the `declare` keyword actually guarantee, and how would you stop this class of failure?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Nothing. An ambient declaration is an unverified promise to the type checker; it emits no JavaScript and never checks that the thing exists. Guarantees have to come from the build or from a runtime-validated module, not from the declaration.

open as a page

A TypeScript package ships `.d.ts` files and sets the `types` field in package.json, but after it adds an `exports` map, consumers on `"moduleResolution": "node16"` report that its types are gone. Why, and how do you fix it?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Under node16 resolution, an exports map takes over entry-point resolution and the top-level types field is no longer consulted. Add a types condition inside exports, listed first in each condition object, or place a matching .d.ts beside every resolved JavaScript file.

open as a page

A half-migrated codebase sets "strict": true in tsconfig.json and tsc reports several thousand errors. How would you get the project to full strict without freezing feature work?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Do not merge the all-at-once change. Stage it: keep strict on but switch individual flags back off and re-enable them one at a time, or scope strict to migrated directories, and make CI hold a baseline error count that is only ever allowed to fall.

open as a page

A dependency imports fine at runtime, but `tsc` reports TS2307 "Cannot find module 'pkg/utils' or its corresponding type declarations" — the package exposes that subpath only through an `exports` map in its package.json, and the tsconfig sets `"moduleResolution": "node10"`. What is going on, and what do `node16`/`nodenext` and `bundler` change?

level: seniorimportance: should knowfreq 50%

basics

~20 s

node10 is the legacy resolution algorithm: it walks node_modules by directory and file name and ignores package.json exports entirely, so a subpath declared only there is invisible to the compiler. node16, nodenext and bundler read exports maps.

open as a page

A TypeScript build that downlevels to ES5 emits the same helper functions — `__extends`, `__awaiter`, `__spreadArray` — at the top of many output files. What does the `importHelpers` compiler option change, what must the package declare, and when is it the wrong choice?

level: seniorimportance: should knowfreq 26%

basics

~20 s

By default the compiler inlines its downleveling helpers into every file that needs them. importHelpers makes it import them from the tslib package instead, so they exist once. tslib then becomes a real runtime dependency, which a published library must list under dependencies.

open as a page

Your team wants to adopt TypeScript's beyond-strict correctness flags — noUncheckedIndexedAccess, exactOptionalPropertyTypes and noUnusedLocals — on a large existing codebase. How would you sequence the rollout, and which flag do you expect to cost the most?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Measure each flag's error count first by enabling it alone on the command line, then adopt one flag per change. Expect noUncheckedIndexedAccess to dominate, since it touches every array index and dictionary lookup, and watch that its fixes are real checks rather than assertions.

open as a page

A TypeScript package's index.ts contains `export { Config } from './config';` where Config is an interface. Built with `tsc` the package works, but built by a per-file transpiler such as esbuild or swc, consumers crash with an error saying './config' provides no export named 'Config'. Why does the same source behave differently, and how do you fix it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

tsc reads ./config, sees Config is an interface, and drops it from the re-export. A per-file transpiler never reads that file, so it emits a real re-export of a binding that the emitted config.js does not have. Fix it with export type, and enable isolatedModules so the compiler catches every such site.

open as a page

You set "strict": true in the tsconfig.json of an established TypeScript codebase and tsc reports several thousand errors. How do you get the project onto strict without a big-bang rewrite?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Split the work by flag and by file. Enable the cheap members of the strict family first, then adopt strictNullChecks and noImplicitAny through a second tsconfig that extends the base, sets strict, and lists a growing set of already-clean files that CI checks alongside the loose build.

open as a page

Your team wants request-scoped user data available as `req.user` throughout a TypeScript service by augmenting the web framework's `Request` interface globally. As the engineer setting the standard, how do you weigh that global augmentation against the alternatives?

level: principalimportance: should knowfreq 32%

basics

~20 s

Global augmentation is convenient but unscoped and unverified: the property appears on every request in every file whether or not the middleware ran. Prefer it only for genuinely universal, optional data, and use distinct types or an explicit context for anything a handler must be able to rely on.

open as a page

You own a large, actively developed JavaScript codebase and the team wants type safety. How would you decide between an incremental TypeScript migration, a rewrite in TypeScript, and staying on JavaScript with types checked by tsc?

level: principalimportance: should knowfreq 35%

basics

~20 s

Incremental is the default because the codebase stays shippable and every step is revertable. A rewrite is only defensible for a small, stable, well-covered module. Staying on typed JavaScript is the right answer when the build cannot change or the code ships as source.

open as a page

You own the tsconfig for a package published to npm and for the application that consumes it. How do you decide `target` and `lib` for each, and what does the TypeScript compiler explicitly not do for you?

level: principalimportance: should knowfreq 38%

basics

~20 s

Pick target from the oldest runtime that must execute the emitted code, and lib from the APIs that runtime actually provides plus any polyfills you ship. The compiler downlevels syntax only: it never polyfills built-ins, and the lib you choose leaks into your published types as a requirement on consumers.

open as a page

What do the triple-slash directives `/// <reference path="..." />`, `/// <reference types="..." />` and `/// <reference lib="..." />` each do in a TypeScript file, and what makes one of them silently do nothing?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

path pulls another file into the compilation, types declares a dependency on a package's ambient type declarations, and lib pulls in a built-in library file such as es2015. A directive placed after any statement is treated as an ordinary comment and ignored.

open as a page

After adding `"types": ["node"]` to compilerOptions in a tsconfig.json, every test file starts failing with "Cannot find name 'describe'". What does the `types` option control, and how does it differ from `typeRoots`?

level: middleimportance: nice to knowfreq 36%

basics

~20 s

The types option is an allow-list for the @types packages that are auto-included as globals. Naming any package suppresses the automatic inclusion of all the others, so the test framework's global declarations disappear. typeRoots changes which folders that automatic inclusion scans.

open as a page

What does TypeScript's `maxNodeModuleJsDepth` compiler option control, why is its default 0, and what goes wrong when you raise it?

level: seniorimportance: nice to knowfreq 16%

basics

~20 s

maxNodeModuleJsDepth sets how many folder levels deep under node_modules TypeScript will load JavaScript files to infer types from. It defaults to 0 so dependency types come from declaration files, not from inferring over dependency source.

open as a page

You lead a team whose build strips TypeScript types without checking them. How would you decide where the type-check gate runs — the editor, a pre-commit hook, or CI — and what does each placement cost?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Treat the editor as feedback, a pre-commit hook as convenience, and a blocking CI job as the only real enforcement. Put the guarantee in CI, keep the other two as fast loops, and run the identical tsc --noEmit command everywhere.

open as a page

You lead a monorepo where `tsc` declaration emit dominates build time. What does TypeScript's `isolatedDeclarations` option require of the code, what does it buy, and how would you decide whether to adopt it?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

isolatedDeclarations, added in TypeScript 5.5, makes the compiler reject exports whose declarations cannot be produced from a single file alone, forcing explicit annotations on the public surface. That guarantee lets other tools emit .d.ts files per file, in parallel, without whole-program inference.

open as a page

showing 31–58 of 58