Course outline · 0% complete

0/26 lessons0%

Course overview →

tsconfig Essentials and strict Mode

lesson 8-1 · ~11 min · 24/26

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 emit
  • module: how import/export is compiled
  • outDir: where compiled .js files go
  • include: which folders to compile
  • strict: 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 | null is the honest signature, and the if narrows it exactly as in Unit 4.
  • Without strictNullChecks, null would be assignable to string and the if could be skipped, which is how the crash in the previous section happens.
  • Changing the target to "linus" sends control to the else branch and prints not 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 like findUser above. The null branch 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 >= 6 are worth checking against a real value.
  • ?? turns a null result into the fallback string without an if, 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.