TypeScript checks a program before execution and can emit JavaScript. Its type annotations are not runtime validators. If a server sends the wrong data, an interface declaration does not repair or reject the payload. Keep the JavaScript behavior you have learned and use the checker to make intended relationships explicit.
This course uses syntax supported by TypeScript 5.9 and an ES2022 output baseline; these are reproducible teaching settings, not a claim about the newest release. In a new exercise folder run npm init -y, then npm install --save-dev typescript@5.9. Commit the resulting lockfile for reproducible installs. Use the project's compiler through npx tsc, rather than accidentally using a different global version.
Configure a small project
Save this as tsconfig.json. strict enables a family of stronger checks. The two additional flags make possibly missing indexed values and optional properties more explicit; they are not implied by strict alone. types: [] keeps unrelated installed ambient type packages out of these standalone examples.
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM"],
"types": [],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noEmitOnError": true,
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}
Create src, then save:
function describe(topic: string, minutes: number): string {
return `${topic}: ${minutes} minutes`;
}
const minutes = 25;
const topics: string[] = ["JavaScript", "TypeScript"];
const first = topics[0];
if (first !== undefined) console.log(describe(first, minutes));
export {};
Run npx tsc -p tsconfig.json. To execute the emitted dist/main.js as an ES module in Node, set "type": "module" in package.json, then run node dist/main.js. It prints JavaScript: 25 minutes. The compiler erased the type annotations. Checking and execution are separate steps; fixing only the emitted file is temporary because the next build regenerates it.
Infer where the value is obvious
The checker infers a number for the numeric binding, so annotating every local variable is unnecessary. Declare useful contracts on function inputs and public results. Use lowercase primitive types such as string, not the boxed String type. Arrays describe element types; tuples describe positional structure. A readonly array prevents mutation through that typed reference but does not freeze the runtime object.
With strict null checks, a value that might be undefined must be handled before it is treated as present. A non-null assertion silences that check without creating a runtime test. Avoid it when a simple branch makes the contract clear. Similarly, any switches off much of the protection you are trying to gain. Use unknown for values that need inspection.
Exercise: add an optional label parameter and choose a default without replacing a deliberately supplied empty string. Then attempt to call describe with a string duration and inspect the compiler error.
Solution guidance: represent a potentially absent parameter with ?, and use ?? if only nullish absence should trigger the default. The string duration should fail type checking. Do not hide that failure with an assertion; fix the call or perform explicit parsing at its boundary.
References: TypeScript basics, strict options.
Previous: DOM, Events and Accessible Interaction. Next: Unions, Narrowing and Runtime Validation.

