Tooling & Compiler
Compiling TypeScript to JavaScript with tsc, tsconfig.json configuration, and strict-mode compiler options.
11 questions
JuniorTheoryVery commonHow do you compile TypeScript into JavaScript?
How do you compile TypeScript into JavaScript?
The TypeScript compiler tsc transpiles .ts files into .js. You configure it with a tsconfig.json whose compilerOptions set target, module, outDir, and strict. Running tsc reports type errors at compile time and emits JavaScript with all types erased.
Common mistakes
- ✗Thinking types are kept in the output —
tscerases them, JavaScript has no types - ✗Assuming browsers run
.tsdirectly without any compile step - ✗Forgetting that
tsconfig.jsonis what drivestsc's behavior
Follow-up questions
- →What does the
targetoption intsconfig.jsoncontrol? - →Does
tscperform any optimizations or only type-check and emit?
MiddleTheoryVery commonWhat does enabling strict mode in tsconfig do?
What does enabling strict mode in tsconfig do?
Setting "strict": true in tsconfig.json turns on a family of strict type-checking flags at once, including strictNullChecks (so null and undefined are not silently assignable to other types) and noImplicitAny (an error on any inferred any). It maximizes type safety and is recommended for new projects.
Common mistakes
- ✗Thinking
strictis one check rather than an umbrella over several flags - ✗Believing it adds runtime null checks — all checks are compile-time only
- ✗Confusing
noImplicitAnywith banninganyentirely (explicitanyis fine)
Follow-up questions
- →What specifically does
strictNullCheckschange about assignability? - →Can you enable individual strict flags without turning on all of
strict?
JuniorTheoryCommonWhat does the target option control in the JavaScript that tsc emits?
What does the target option control in the JavaScript that tsc emits?
target picks the ECMAScript version tsc emits. Syntax newer than the target is downleveled into equivalent older syntax; syntax the target already understands is emitted as is. It also selects the default lib declarations, but it never polyfills a missing runtime API.
Common mistakes
- ✗Confusing
target(the syntax level) withmodule(the emitted module format) - ✗Expecting
targetto add polyfills — it downlevels syntax, not runtime APIs - ✗Forgetting
targetalso picks the defaultlibdeclarations
Follow-up questions
- →Why does a low
targetstill let you callPromisewithout any polyfill being emitted? - →How does
liblet you override the declarations thattargetselected by default?
JuniorTheoryCommonWhy do you turn on sourceMap, and what does the compiler produce?
Why do you turn on sourceMap, and what does the compiler produce?
sourceMap: true makes tsc emit a .js.map beside every .js, mapping each emitted position back to the original .ts line and column. Debuggers and stack traces then point at your TypeScript source instead of the compiled output. The map is a separate build artifact and changes nothing at runtime.
Common mistakes
- ✗Thinking a source map changes the emitted JavaScript's behavior — it is a separate artifact
- ✗Believing the map stores types rather than position mappings
- ✗Assuming the browser recompiles the
.tsrather than just mapping positions
Follow-up questions
- →What does
inlineSourcesadd on top of a plain source map? - →Why do teams avoid shipping
.js.mapfiles with a public production bundle?
MiddleTheoryCommonWhat does declaration: true emit, and why must a published library have it?
What does declaration: true emit, and why must a published library have it?
declaration: true makes tsc write a .d.ts beside every emitted .js, carrying the public type signatures with all implementation bodies stripped. A package ships only JavaScript, so without those files a consumer sees any; the types field in package.json points at the entry .d.ts.
Common mistakes
- ✗Thinking a
.d.tscarries implementation code rather than stripped signatures - ✗Assuming a consumer can type-check a package from its emitted
.jsalone - ✗Forgetting
package.jsonmust point at the entry.d.tsvia thetypesfield
Follow-up questions
- →What breaks when an exported signature references a type that is not itself exported?
- →How does a
declarationMapimprove go-to-definition for a consumer of the package?
MiddleTheoryCommonWhat does the declare keyword do, and what is a .d.ts file?
What does the declare keyword do, and what is a .d.ts file?
declare introduces an ambient declaration — it tells the compiler about a value, type, function, or module that exists elsewhere (a global, a plain-JS library, a script-injected variable) without providing an implementation. It is type-only and emits no JavaScript. Declaration files (.d.ts) collect these ambient declarations to describe the shape of code the compiler cannot see, such as untyped libraries.
Common mistakes
- ✗Thinking
declareemits or runs code — it is type-only with no JavaScript output - ✗Believing a
.d.tsfile contains implementations rather than type signatures - ✗Assuming
declarere-validates a value at runtime rather than just at compile time
Follow-up questions
- →How would you add types for a plain-JavaScript library that ships none?
- →When do you use
declare globalversus a module declaration?
MiddleTheoryCommonHow does noImplicitAny relate to the strict umbrella flag?
How does noImplicitAny relate to the strict umbrella flag?
noImplicitAny is one member of the strict family: it errors wherever the compiler silently falls back to any, as on an unannotated parameter. strict: true switches the whole family on at once, and an explicitly set member overrides the umbrella.
Common mistakes
- ✗Treating
strictandnoImplicitAnyas unrelated flags rather than umbrella and member - ✗Believing
strict: truelocks its members on and cannot be relaxed one at a time - ✗Confusing
noImplicitAnywith a ban on writinganyexplicitly
Follow-up questions
- →Which other checks does the strict family switch on besides
noImplicitAny? - →Where can an implicit
anystill slip in even withnoImplicitAnyenabled?
MiddleTheoryCommonWhy do most projects enable skipLibCheck, and what does it trade away?
Why do most projects enable skipLibCheck, and what does it trade away?
skipLibCheck: true stops tsc from type-checking the contents of .d.ts files — your own and everything under node_modules. That cuts build time and silences errors from third-party declarations you cannot fix. In exchange, a real mistake inside a declaration goes unreported; your .ts source is still checked.
Common mistakes
- ✗Thinking it drops library types entirely — call sites are still checked against them
- ✗Believing it touches only
node_modulesand never your own emitted.d.ts - ✗Assuming it is free — a real error inside a declaration file is silenced
Follow-up questions
- →How can
skipLibCheckhide a conflict between two copies of a library's types? - →When is it worth turning off, and what usually breaks the moment you do?
MiddleTheoryCommonWhat bug class does strictNullChecks eliminate, and by what rule?
What bug class does strictNullChecks eliminate, and by what rule?
Without it, null and undefined are assignable to every type, so a string can secretly hold one and dereferencing crashes at runtime. Enabling it removes both from all other types: they must appear in an explicit union like string | null, forcing a narrowing check.
Common mistakes
- ✗Thinking it adds runtime null guards — the check is compile-time only
- ✗Forgetting it covers
undefinedtoo, not justnull - ✗Expecting it to ban
nulloutright instead of demanding an explicit union
Follow-up questions
- →How do you narrow a
string | nullso the compiler accepts a property access? - →What does the non-null assertion
!do to this check, and why is it risky?
MiddleTheoryOccasionalWhat bug does noUncheckedIndexedAccess catch, and what does it change?
What bug does noUncheckedIndexedAccess catch, and what does it change?
Indexing an array or an index-signature type can miss, yet by default arr[i] is typed plain T and may be dereferenced right away. The flag adds undefined to every indexed access, so the type becomes T | undefined and a check comes first. It is opt-in, not part of strict.
Common mistakes
- ✗Assuming
strictalready enables it — it is a separate opt-in flag - ✗Thinking it adds a runtime bounds check rather than widening the static type
- ✗Forgetting it applies to plain array element access, not only index signatures
Follow-up questions
- →Why is this flag deliberately left out of the
strictfamily? - →How do destructuring and a
for...ofloop avoid the extraundefined?
SeniorTheoryOccasionalHow do you add hand-written .d.ts types to a JavaScript package that ships none?
How do you add hand-written .d.ts types to a JavaScript package that ships none?
Author the declarations by hand and either ship them inside the package with the types field in package.json, or publish them separately as @types/<name>, which the compiler resolves automatically. Nothing verifies a .d.ts against the real JavaScript, so drift from it is silent.
Common mistakes
- ✗Thinking
tscverifies a hand-written.d.tsagainst the package's real JavaScript - ✗Believing a JavaScript package must be rewritten in TypeScript before it can be typed
- ✗Forgetting the
typesfield when the declarations ship inside the package itself
Follow-up questions
- →How does the compiler choose between a bundled
.d.tsand an@types/<name>package? - →What keeps a hand-written declaration in sync as the JavaScript package releases updates?