The project control panel
Real projects configure the compiler in one file, tsconfig.json, at the project root. A minimal, sane starting point:
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"strict": true,
"outDir": "dist"
},
"include": ["src"]
}target: which JavaScript version to emitmodule: how import/export is compiledoutDir: where compiled.jsfiles goinclude: which folders to compilestrict: the one that matters most
strict: true switches on a family of checks. The two headline members: noImplicitAny makes unannotated parameters an error instead of a silent any (remember lesson 1-3), and strictNullChecks makes null and undefined their own types instead of members of every type.
What strictNullChecks changes
Without it, this compiles and crashes. Tony Hoare, who introduced null references into programming languages in 1965, later called them his "billion-dollar mistake", his estimate of what crashes exactly like this one have cost the industry:
const found: string = maybeFind(); // might be null! console.log(found.toUpperCase()); // runtime crash
With strictNullChecks, a function that can fail must say so in its type as string | null, and callers must narrow before use, which is exactly the Unit 4 discipline:
if (found !== null) { console.log(found.toUpperCase()); }
Every new project should start with strict: true. Loosening a check later is easy, while tightening one on a grown codebase means fixing hundreds of errors at once.
A search with an honest return type
findUser returns string | null, and the caller narrows before using the result.
function findUser(names: string[], target: string): string | null { for (const n of names) { if (n === target) { return n; } } return null; } const found = findUser(["ada", "grace"], "ada"); if (found !== null) { console.log("found " + found.toUpperCase()); } else { console.log("not found"); }
Output
found ADA
What strict mode is enforcing here
- The union return type
string | nullis the honest signature, and theifnarrows it exactly as in Unit 4. - Without
strictNullChecks,nullwould be assignable tostringand theifcould be skipped, which is how the crash in the previous section happens. - Changing the target to
"linus"sends control to theelsebranch and printsnot found. Both paths are written, because the type forced both to be considered.
firstLongName: a null result closed by ??
firstLongName(names: string[]): string | null returns the first name of at least 6 characters, or null when none qualifies.
function firstLongName(names: string[]): string | null { for (const n of names) { if (n.length >= 6) { return n; } } return null; } console.log(firstLongName(["ada", "grace", "marissa"]) ?? "none found"); console.log(firstLongName(["bo", "al"]) ?? "none found");
Output
marissa none found
Reading the code
- The honest return type is
string | null, exactly likefindUserabove. Thenullbranch is a real outcome, so it belongs in the signature. "grace"has 5 letters, so the first qualifying name is"marissa". Off-by-one boundaries like>= 6are worth checking against a real value.??turns anullresult into the fallback string without anif, which is the lesson 4-4 idiom applied at the call site rather than inside the function.
The flag behind implicit-any errors
The flag is noImplicitAny, and it is included in strict.
noImplicitAny closes the silent any hole from lesson 1-3: an unannotated parameter becomes a compile error rather than an unchecked value. Turning on strict enables it along with strictNullChecks and the rest of the family, which is why most projects set strict and never think about the individual flags.
The other options in a typical tsconfig.json are not involved. target, outDir, and include control what gets emitted and from where, not how strictly the code is checked.