TypeScript · lesson 1 of 21
Beginner
6Intermediate
12- 7 Type Aliases in TypeScript
- 8 Classes in TypeScript
- 9 Generics in TypeScript
- 10 Utility Types in TypeScript
- 13 Modules and Declaration Files in TypeScript
- 14 Error Handling in TypeScript
- 15 Async TypeScript
- 16 Testing TypeScript
- 17 Design Patterns in TypeScript
- 18 React with TypeScript
- 19 Node.js with TypeScript
- 21 TypeScript Interview Prep
Also part of TypeScript
2Introduction to TypeScript
Understand what TypeScript is, why it exists, how it differs from JavaScript, and where it fits in the modern web ecosystem.
By SkillByExample · Updated
What Is TypeScript?
TypeScript is a statically typed superset of JavaScript developed and maintained by Microsoft. You annotate variables, function parameters and return values with types, and the TypeScript compiler (tsc) checks those annotations before your code runs. Every example here was run with TypeScript 7.0.2 on Node v24.18.1, in a folder with no tsconfig.json.
Start with plain JavaScript. Save this as plain.js and run it:
function add(a, b) {
return a + b;
}
console.log(add(1, 2));
console.log(add(1, "hello"));
$ node plain.js
3
1hello
The second call is a bug, but JavaScript doesn’t complain. + sees a string, so it concatenates instead of adding, and the result is a wrong answer rather than a crash.
Now the same function with types, saved as add.ts:
function add(a: number, b: number): number {
return a + b;
}
console.log(add(1, 2));
console.log(add(1, "hello"));
$ npx tsc add.ts
add.ts(6,20): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
tsc points at line 6, column 20, which is the "hello" argument, before anything runs. That is the core value of TypeScript: the mistake is reported where you wrote it, not when a user hits that code path.
Why TypeScript Exists
JavaScript was designed for small scripts in web pages. As applications grew into hundreds of thousands of lines across large teams, the lack of types created real, costly problems:
- Refactoring is risky. Renaming a function or changing its signature in a large JS codebase means manually hunting every call site. TypeScript’s compiler does that for you automatically and flags every place that breaks.
- Tooling is limited. Without types, editors cannot reliably autocomplete, detect unused variables, or show inline documentation. Every object is opaque until you read the source.
- Bugs surface late. A typo in a property name or passing the wrong argument type shows up only when a user hits that code path at runtime — which may be rarely, or in a critical flow.
TypeScript addresses all three. It was first released in 2012 and has become the dominant choice for large-scale JavaScript projects.
TypeScript vs JavaScript
Understanding the key differences helps you know when TypeScript is pulling its weight and when you might prefer plain JS for a small script.
| Feature | JavaScript | TypeScript |
|---|---|---|
| Type system | Dynamic, checked at runtime | Static, checked by tsc before running |
| Syntax | ES standard | ES standard + type annotations |
| Runs in browsers | Directly | After compiling or bundling to JS |
| Runs in Node.js | Directly | Directly since Node 22.18 (types stripped, not checked), or after compiling |
| Tooling (autocomplete, refactoring) | Limited | Excellent |
| Catch errors early | No | Yes, if you run tsc |
The “not checked” in that table matters. Node 24 can run add.ts itself, with no compile step:
$ node add.ts
3
1hello
Node removed the annotations and ran what was left, which is exactly plain.js. It never looked at the types, so the bug printed 1hello again. Running TypeScript and type-checking it are two separate steps, and only tsc (or your editor, which uses the same checker) does the second one.
A Realistic Example
This example shows how TypeScript prevents a whole class of bug that is easy to introduce and hard to track down in plain JavaScript. Save it as welcome.ts:
interface User {
id: number;
name: string;
email: string;
}
function sendWelcomeEmail(user: User): void {
console.log(`Sending email to ${user.email}`);
}
const newUser = { id: 1, name: "Alice", email: "alice@example.com" };
sendWelcomeEmail(newUser);
// An incomplete object: Bob has no email
sendWelcomeEmail({ id: 2, name: "Bob" });
$ npx tsc welcome.ts
welcome.ts(15,18): error TS2741: Property 'email' is missing in type '{ id: number; name: string; }' but required in type 'User'.
Alice’s call compiles even though newUser never mentions User. TypeScript compares shapes, not names, and her object has every property User needs. Bob’s object is missing email, and the error names that exact property. Without TypeScript, this bug would only show up at runtime as an email sent to undefined (Practice 2 runs it and shows that).
The TypeScript Ecosystem
TypeScript is deeply integrated into the modern JavaScript ecosystem, which means you benefit from it immediately in any standard project:
- Frameworks: React, Angular, Vue, Svelte, Next.js, Nuxt, and Remix all have first-class TypeScript support. Many are written in TypeScript themselves.
- Runtimes: Node.js 22.18+ runs
.tsfiles by stripping their types, as shown above, andtsxdoes the same for syntax Node can’t strip. Deno and Bun run TypeScript without any setup. None of them type-check;tscstill does that. - Compiler: TypeScript 7 (
typescript@latestsince July 2026) is a native rewrite of the compiler in Go. It checks the same language much faster than the JavaScript-based TypeScript 6, and turns options that 6.0 deprecated, such as--target ES5, into errors. - Package ecosystem: Most popular npm packages ship with
.d.tsdeclaration files or have community-maintained@types/*packages on DefinitelyTyped, so you get types for libraries you didn’t write. - Tooling: VS Code, WebStorm, and other editors provide full IntelliSense, inline errors, and refactoring tools powered by the TypeScript language server.
Compilation Pipeline
tsc does two jobs: it type-checks your .ts files, then it writes plain .js files with the types removed. Save this as pipeline.ts, compile it, and look at what it produced:
interface Point {
x: number;
y: number;
}
const start: Point = { x: 0, y: 0 };
const distance = (p: Point): number => Math.hypot(p.x, p.y);
console.log(distance({ x: 3, y: 4 }), distance(start));
$ npx tsc pipeline.ts
$ cat pipeline.js
"use strict";
const start = { x: 0, y: 0 };
const distance = (p) => Math.hypot(p.x, p.y);
console.log(distance({ x: 3, y: 4 }), distance(start));
$ node pipeline.js
5 0
The interface is gone completely, because it only ever existed for the checker. The : Point and : number annotations are gone too. What’s left is the same JavaScript you would have written by hand, plus a "use strict" line, because TypeScript 6 and later compile in strict mode by default.
The compiled JavaScript can also target older environments. The target option decides which newer syntax tsc rewrites into older syntax, and module decides how import/export are written out. You control both through tsconfig.json, covered in the next tutorial.
Key Takeaways
- TypeScript adds a static type system to JavaScript without changing the runtime behavior.
- Errors caught by the compiler never reach your users, but only if something runs the compiler.
node file.tsruns code without checking it. tscstill writes JavaScript when there are type errors, unless you setnoEmitOnError.- The tooling improvements — autocomplete, safe refactoring, inline docs — are often as valuable as the type safety itself.
- TypeScript compiles to plain JavaScript, so it runs everywhere JavaScript runs.
Practice
You need Node.js and the TypeScript compiler (npm install -D typescript). The solutions were
run with TypeScript 7.0.2 on Node v24.18.1, with no tsconfig.json in the folder.
- Many older tutorials show the JavaScript
addand the typedaddtogether in one snippet. Put both functions in one file,both.ts, with the two calls after them, and compile it. Is the error the TS2345 you saw above?
Solution
// JavaScript — no type information, no safety net
function add(a, b) {
return a + b;
}
// TypeScript — types are explicit, mistakes are caught immediately
function add(a: number, b: number): number {
return a + b;
}
add(1, 2); // fine
add(1, "hello"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
$ npx tsc both.ts
both.ts(2,10): error TS2393: Duplicate function implementation.
both.ts(2,14): error TS7006: Parameter 'a' implicitly has an 'any' type.
both.ts(2,17): error TS7006: Parameter 'b' implicitly has an 'any' type.
both.ts(7,10): error TS2393: Duplicate function implementation.
No. One file can’t hold two bodies for the same function name, so tsc reports TS2393 on both
declarations. Strict mode (on by default) also rejects the untyped a and b with TS7006. The
calls are checked against the first add, whose parameters are implicitly any, so "hello" is
accepted and the TS2345 the comment promises never appears. (Swap the two functions and it does,
on line 12, alongside the same TS2393s.) Comments can lie. Run the code.
- Compile
welcome.tsfrom the realistic example, then run thewelcome.jsthattscproduced anyway. What does Bob’s line print?
Solution
$ npx tsc welcome.ts
welcome.ts(15,18): error TS2741: Property 'email' is missing in type '{ id: number; name: string; }' but required in type 'User'.
$ node welcome.js
Sending email to alice@example.com
Sending email to undefined
tsc reported the error and still wrote welcome.js, because type errors don’t block output by
default. Running it shows the bug the lesson describes: no crash, just undefined going out as
an email address.
- Make a different mistake in a copy called
typo.ts: delete Bob’s call so only Alice’s call remains, written inline, then misspell the property inside the function asuser.emial. Compile it and run the result.
Solution
interface User {
id: number;
name: string;
email: string;
}
function sendWelcomeEmail(user: User): void {
console.log(`Sending email to ${user.emial}`);
}
sendWelcomeEmail({ id: 1, name: "Alice", email: "alice@example.com" });
$ npx tsc typo.ts
typo.ts(8,40): error TS2551: Property 'emial' does not exist on type 'User'. Did you mean 'email'?
$ node typo.js
Sending email to undefined
Because the compiler knows every property of User, it can suggest the right name. At runtime the
typo becomes a silent undefined, even though the caller passed a correct object.
-
Watch the
targetoption rewrite syntax. Save this astarget.ts.??(nullish coalescing) arrived in ES2020. Compile it with--target ES5, then--target ES2019, then--target ES2020, and look attarget.jsafter each run.const greet = (name?: string): string => `Hello, ${name ?? "stranger"}`; console.log(greet("Ada"), greet());
Solution
$ npx tsc --target ES5 target.ts
error TS5108: Option 'target=ES5' has been removed. Please remove it from your configuration.
$ npx tsc --target ES2019 target.ts
$ cat target.js
"use strict";
const greet = (name) => `Hello, ${name !== null && name !== void 0 ? name : "stranger"}`;
console.log(greet("Ada"), greet());
$ npx tsc --target ES2020 target.ts
$ cat target.js
"use strict";
const greet = (name) => `Hello, ${name ?? "stranger"}`;
console.log(greet("Ada"), greet());
$ node target.js
Hello, Ada Hello, stranger
TypeScript 6.0 deprecated ES5 output and TypeScript 7 removed it, so the oldest target left is
ES2015. For ES2019, tsc rewrites ?? into the equivalent !== null && !== void 0 check. For
ES2020 it keeps ?? as written, because that version of JavaScript already has it. Both builds
drop the ?: string and : string annotations, and both print the same thing.