What you'll learn
Quick Answer
TypeScript adds type annotations that are checked at compile time and removed before the code runs. It catches typos, wrong arguments and undefined access before you ship. Enable strict mode, and avoid any except as a temporary measure.
It is a checker, not a runtime
The single most important fact: types are erased before the code runs. The browser never sees them. TypeScript compiles to plain JavaScript, and every check happens at compile time.
So TypeScript cannot validate data arriving from an API at runtime. If you declare a response as User and the server sends something else, nothing stops it. Types describe what you expect; runtime validation is a separate job.
Every valid JavaScript file is valid TypeScript, which means adoption can be gradual. Rename a file to .ts and it still works — you then add types where they help.
The errors it catches, exactly as reported
Given this file:
let count: number = 5;
count = "hello";
interface User { name: string; age: number; }
const u: User = { name: "Asha" };
function greet(n: string) { return `Hi ${n}`; }
greet(42);
const maybe: string | undefined = undefined;
console.log(maybe.length);
the compiler reports:
error TS2322: Type 'string' is not assignable to type 'number'.
error TS2741: Property 'age' is missing in type '{ name: string; }'
but required in type 'User'.
error TS2345: Argument of type 'number' is not assignable to
parameter of type 'string'.
error TS18048: 'maybe' is possibly 'undefined'.
Four bugs found without running anything. The last one is the most valuable — "Cannot read properties of undefined" is among the most common JavaScript runtime errors, and TypeScript turns it into a compile-time message.
The syntax you need on day one
let name: string = "Asha";
let age: number = 20;
let active: boolean = true;
let marks: number[] = [91, 88];
function add(a: number, b: number): number {
return a + b;
}
interface User {
name: string;
age: number;
city?: string; // optional
}
type Status = "pending" | "active" | "closed";
Most annotations are unnecessary. TypeScript infers types, so let name = "Asha" is already string. Annotate function parameters and return types, and object shapes; let inference handle local variables.
That union type at the end is one of the nicest features. Status accepts only those three strings — a typo like "activ" is a compile error, which replaces a whole category of constant-string bugs.
any switches the checker off
let data: any = getFromApi();
data.whatever.nonsense(); // no error, crashes at runtime
any means "stop checking this". It is occasionally necessary and frequently overused — a codebase liberally sprinkled with any has the syntax of TypeScript and the safety of JavaScript, plus a build step.
When you genuinely do not know the type, prefer unknown. It accepts anything but forces you to narrow before use:
let data: unknown = getFromApi();
if (typeof data === "string") {
console.log(data.toUpperCase()); // allowed here
}
Turn on strict in tsconfig.json from the start. It enables null checking and stops implicit any, which is where most of the value is. Enabling it later on a large codebase produces hundreds of errors at once and usually gets abandoned.
When it is worth it
Honestly: for a fifty-line script, it is overhead. The value grows with the size of the codebase and the number of people touching it.
Where it clearly pays off: refactoring, because renaming a field surfaces every place that used it; working in a team, because function signatures document themselves; and editor support, since autocomplete genuinely knows what a variable holds rather than guessing.
The costs are real too — a build step, more verbose code, and occasional fights with the type system over something you know is fine.
For students: learn JavaScript properly first, including the modern features. TypeScript on top of shaky JavaScript means debugging two things at once. Once JavaScript is comfortable, TypeScript takes a weekend and appears in most professional front-end job listings.
