After this lesson you can
- Say what strictNullChecks changes about every type
- Handle a value that may be null without reaching for a cast
- Recognise the casts that quietly disable the checker
Without strict, TypeScript is a linter with opinions. With it, the
compiler starts catching the errors that actually reach production. Turn it
on in tsconfig.json and leave it on.
{ "compilerOptions": { "strict": true } }
strictNullChecks
The single most valuable flag in the set. Without it, null and undefined
are members of every type, so string silently means "a string, or nothing
at all". With it, they are separate:
function greet(name: string | null): string {
return `Hi ${name.toUpperCase()}`; // error: name may be null
}
You then handle it, and the handling is visible in the code rather than living in someone's memory:
if (name === null) return "Hi there";
return `Hi ${name.toUpperCase()}`;
?. and ?? are for the cases where a default is genuinely right:
const display = user.profile?.name ?? user.email;
Try it
interface Profile { name?: string }interface User { email: string; profile?: Profile } function display(user: User): string { // Both hops may be missing, so both are optional-chained, and the // fallback is a real value rather than "undefined" printed to a page. return user.profile?.name ?? user.email;} export function run(): string[] { return [ display({ email: "nino@example.com" }), display({ email: "ana@example.com", profile: {} }), display({ email: "luka@example.com", profile: { name: "Luka" } }), ];}The escape hatches, and what they cost
as Type— an assertion. You are telling the compiler you know better. If you are wrong, nothing catches it and the failure surfaces somewhere else.!— the non-null assertion.user!.namesays "trust me". The same deal, in one character, which is what makes it easy to scatter.any— the checker off entirely for that value, and for anything derived from it.
All three are occasionally the right answer, usually at a boundary where you genuinely know something the compiler cannot. Each one is also a place where a future bug will not be caught, so each one deserves a comment saying why it is safe.
The habit worth building: when the compiler complains, ask what it has noticed before you ask how to silence it. It is usually right.
What strict does and does not switch on
strict is a family: strictNullChecks, noImplicitAny (an unannotated
parameter is an error instead of a silent any), useUnknownInCatchVariables
(a caught error is unknown), strictFunctionTypes,
strictPropertyInitialization and a few more. Two useful checks are not
in it and must be turned on separately: noUncheckedIndexedAccess (an
array or record lookup may be ) and
(an optional property may be absent, but not explicitly ).
interface Settings { retries?: number; label?: string;} export function run(): unknown[] { const s: Settings = { retries: 0, label: "" }; // || replaces every falsy value; ?? replaces only null and undefined. return [s.retries || 3, s.retries ?? 3, s.label || "untitled", s.label ?? "untitled"];}What interviewers ask
- What does
strictNullCheckschange?nullandundefinedstop being members of every type; a value that may be missing has to say so in its type and be handled before use. - Which useful checks does
strictleave off?noUncheckedIndexedAccessandexactOptionalPropertyTypesare the two usually named. - When is the non-null assertion
!acceptable? When you know something the compiler cannot (a value set by a framework before use), with a comment — and never as a reflex to silence an error.
Exercise · Pro
The last word, or an honest null
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade