JavaScript and TypeScript course · Module 6: TypeScript Basics

What is TypeScript and its advantages

7 min read
In this lesson6

Introduction: The problem with the dinosaur park

Welcome to a new chapter of our Jurassic Park adventure! Imagine the following situation: the park's programmers created an access control system for the dinosaur enclosures, designed to protect both guests and employees. Everything worked great until one moment...

1// access-control-system.js
2
3function grantAccess(employee, accessLevel) {
4  if (employee.permissions >= accessLevel) {
5    return true;
6  }
7  return false;
8}
9
10// Elsewhere in the system
11const drMalcolm = {
12  firstName: "Ian",
13  lastName: "Malcolm",
14  role: "Mathematician"
15  // oops... missing the 'permissions' field!
16};
17
18// Attempt to access the dinosaur enclosure
19const accessToT_Rex = grantAccess(drMalcolm, 5);
20console.log("Access to T-Rex enclosure:", accessToT_Rex); // false, but no error!
21
22// Later in the code...
23if (accessToT_Rex) {
24  open_gate_to_enclosure('t-rex');  // This operation will never execute
25} else {
26  console.log("Dr. Malcolm has no access"); // This will run, but nobody noticed the bug
27}

Did you notice what went wrong? The drMalcolm object was missing the permissions property, causing the system to incorrectly deny him access! In JavaScript this situation generated no error - the permissions property was simply undefined, which when compared to a number gave a result of false.

Now imagine the consequences of this situation in a real dinosaur park:

  1. Dr. Malcolm cannot enter his work zone
  2. Security systems don't work as they should
  3. Worse - nobody knows there's a problem!

This is exactly where TypeScript comes in handy.

What is TypeScript?

TypeScript is a superset of JavaScript that adds optional static typing. It was created by Microsoft and is developed as an open source project.

Think of TypeScript as an advanced security system for Jurassic Park:

  • JavaScript is a park without cameras and sensors - everything works until something goes wrong
  • TypeScript is a park equipped with cameras, motion sensors and an alarm system that detects problems BEFORE the dinosaurs escape from their enclosures

The same situation in TypeScript would look like this:

1// access-control-system.ts
2
3// We define an interface specifying what an employee object should look like
4interface Employee {
5  firstName: string;
6  lastName: string;
7  role: string;
8  permissions: number; // Required field!
9}
10
11function grantAccess(employee: Employee, accessLevel: number): boolean {
12  if (employee.permissions >= accessLevel) {
13    return true;
14  }
15  return false;
16}
17
18// Elsewhere in the system
19const drMalcolm = {
20  firstName: "Ian",
21  lastName: "Malcolm",
22  role: "Mathematician"
23  // TypeScript will catch the error: Property 'permissions' is missing in type
24  // '{ firstName: string; lastName: string; role: string; }' but required in type 'Employee'.
25};
26
27// This line will cause a compilation error - TypeScript will protect us!
28const accessToT_Rex = grantAccess(drMalcolm, 5);

In the above example TypeScript will detect the error at the code-writing stage, before the software runs. It's like a warning system alerting us that the enclosure gate is open, before the dinosaur has a chance to escape!

The most important advantages of TypeScript

1. Early error detection

Like the monitoring system in Jurassic Park, TypeScript detects potential problems even at the code-writing stage:

1function feedDinosaur(dinosaur: { species: string; dietType: string; }): void {
2  let food;
3
4  if (dinosaur.dietType === 'herbivore') {
5    food = 'fern leaves';
6  } else if (dinosaur.dietType === 'carnivore') {
7    food = 'meat';
8  }
9
10  console.log(`Feeding ${dinosaur.species} with food: ${food}`);
11}
12
13// TypeScript will warn that the typo 'herbivro' matches no condition
14const triceratops = { species: 'Triceratops', dietType: 'herbivro' };
15feedDinosaur(triceratops);
16// Without TypeScript this dinosaur would receive "undefined" as food!

2. Better code documentation

TypeScript acts like a detailed inventory of all dinosaurs in the park:

1// Without TypeScript - what are these parameters? What does this function return?
2function createEnclosure(id, name, capacity, fenceType) {
3  //
4}
5
6// With TypeScript - instant documentation!
7function createEnclosure(
8  id: number,
9  name: string,
10  capacity: number,
11  fenceType: 'standard' | 'electric' | 'reinforced'
12): { id: number; status: 'active' | 'under construction' | 'closed' } {
13  //
14  return { id, status: 'under construction' };
15}

3. Better IDE support (code completion)

Thanks to TypeScript, your development environment (IDE) will know exactly what operations you can perform on a given object:

1const dinosaur = { name: 'Rex', species: 'T-Rex', weight: 8000 }
2
3dinosaur.name.toUpperCase() // after the dot the editor suggests name, species and weight, after the second dot the string methods
4dinosaur.height // error: Property 'height' does not exist on type '{ name: string; species: string; weight: number; }'

It's like a map of Jurassic Park with detailed directions showing where the individual enclosures are and what dinosaurs are kept in them.

4. Safer refactoring

When you make changes to the park management system, TypeScript warns you about all places that need updating:

1// Before refactoring
2interface Dinosaur {
3  id: number;
4  species: string;
5  dangerous: boolean;
6}
7
8// After refactoring - changing the field type
9interface Dinosaur {
10  id: number;
11  species: string;
12  threatLevel: number; // 1 to 10 instead of boolean
13}
14
15// TypeScript will point to all places where we used "dangerous"
16// and now we need to use "threatLevel"

5. Types for external libraries

Even if you use external libraries written in plain JavaScript, you can use type definition files that provide code completion and type checking:

1// Installing types for the DNA analysis library
2// npm install --save-dev @types/dino-dna-analyzer
3
4import { analyzeDNA } from 'dino-dna-analyzer';
5
6// Now TypeScript knows the types of parameters and return values
7const result = analyzeDNA({
8  sequence: 'ACGTACGT',
9  baseSpecies: 'frog'
10});
11
12console.log(result.completeness); // TypeScript knows the available fields

6. Support for modern JavaScript features

TypeScript lets you use the latest JavaScript features even if the target environment doesn't support them:

1// Using modern JavaScript syntax
2const dinosaurs = ['T-Rex', 'Velociraptor', 'Triceratops'];
3const [mostDangerous, ...rest] = dinosaurs;
4
5// Optional chaining and nullish coalescing
6const data = {
7  park: {
8    // sector may not exist
9  }
10};
11
12const sectorName = data.park?.sector?.name ?? "Unknown sector";

7. Gradual migration

One of the biggest advantages of TypeScript is the ability to gradually introduce it to existing JavaScript projects:

  1. Change the file extension from .jsto.ts
  2. Initially ignore errors using // @ts-ignore or the any type
  3. Gradually add types to the code, starting with the most critical parts

It's like gradually upgrading the security systems in Jurassic Park, sector by sector, instead of shutting down the entire park for renovations.

Limitations of TypeScript

Of course, like every technology, TypeScript has its limitations:

  1. Additional compilation step - TypeScript code must be converted to JavaScript before running
  2. Steep learning curve - for beginning JavaScript programmers, learning types can be a challenge
  3. False sense of security - TypeScript detects type-related errors, but doesn't protect against logic errors
  4. The any type - you can "cheat" the type system by using any, which negates TypeScript's advantages

When is TypeScript worth using?

TypeScript works best in the following scenarios:

  1. Large projects with many programmers - like managing all of Jurassic Park
  2. Long-term projects - when code will be maintained for a long time
  3. Complex business applications - when errors can have serious consequences
  4. Libraries and Open Source tools - to make it easier for other programmers to use your code

Summary

TypeScript is like an advanced security system for Jurassic Park:

  • JavaScript: "We have dinosaurs in cages, everything should be fine."
  • TypeScript: "Every dinosaur is monitored, we have an alert at the slightest anomaly, and employees have clear instructions."

By adding typing to JavaScript code, you gain:

  • Earlier error detection
  • Better documentation
  • More efficient programming tools
  • Safer refactoring

In the next lessons we'll start exploring the TypeScript type system in detail and see how we can use it to build more reliable systems - ones that wouldn't let dinosaurs escape from the park!

Code for this lesson: index.ts
1// TypeScript in Jurassic Park - Introduction and basics
2console.log("Welcome to TypeScript in Jurassic Park!");
3console.log("Let's learn about TypeScript and its benefits for safe code\n");
4
5// ===========================================
6// 1. Problem with JavaScript - no types
7// ===========================================
8console.log("=== 1. THE PROBLEM WITH JAVASCRIPT ===");
9
10// JavaScript - problems with types
11function grantAccessJS(employee: any, accessLevel: any): boolean {
12  // JavaScript has no type checking
13  if (employee.permissions >= accessLevel) {
14    return true;
15  }
16  return false;
17}
18
19// Unsafe data - no checking
20const drMalcolmJS: any = {
21  name: "Ian Malcolm",
22  role: "Mathematician"
23  // The 'permissions' field is missing!
24};
25
26// This executes without error but returns an incorrect result
27const accessJS = grantAccessJS(drMalcolmJS, 5);
28console.log("JavaScript access result:", accessJS); // undefined >= 5 = false
29
30console.log("JavaScript allows type errors at runtime");
31console.log("Hard debugging");
32console.log("No IntelliSense/autocomplete\n");
33
34// ===========================================
35// 2. TypeScript - solving the problem
36// ===========================================
37console.log("=== 2. TYPESCRIPT - THE SOLUTION ===");
38
39// Interface definition for an employee
40interface Employee {
41  name: string;
42  role: string;
43  permissions: number;
44  department?: string; // Optional field
45}
46
47// A function with TypeScript types
48function grantAccess(employee: Employee, accessLevel: number): boolean {
49  if (employee.permissions >= accessLevel) {
50    return true;
51  }
52  return false;
53}
54
55// Correct data with types
56const drMalcolm: Employee = {
57  name: "Ian Malcolm",
58  role: "Mathematician",
59  permissions: 3
60};
61
62const accessTS = grantAccess(drMalcolm, 5);
63console.log("TypeScript access result:", accessTS);
64
65console.log("TypeScript checks types at compile time");
66console.log("Better IntelliSense and autocomplete");
67console.log("Early error detection\n");
68
69// ===========================================
70// 3. Basic TypeScript types
71// ===========================================
72console.log("=== 3. BASIC TYPESCRIPT TYPES ===");
73
74// Primitive types
75let dinosaurName: string = "Tyrannosaurus Rex";
76let dinosaurAge: number = 68000000;
77let isExtinct: boolean = true;
78let unknownData: unknown = "mysterious data";
79let nothingHere: null = null;
80let notDefined: undefined = undefined;
81
82// Arrays
83let dinosaurSpecies: string[] = ["T-Rex", "Triceratops", "Velociraptor"];
84let dangerLevels: Array<number> = [9, 6, 8]; // Alternative syntax
85
86// Tuple - array with fixed length and types
87let dinosaurInfo: [string, number, boolean] = ["T-Rex", 8000, true];
88
89// Enum - numbered constants
90enum SecurityLevel {
91  Green = 1,
92  Yellow = 2,
93  Orange = 3,
94  Red = 4,
95  Critical = 5
96}
97
98let currentSecurity: SecurityLevel = SecurityLevel.Orange;
99console.log("Current security level:", SecurityLevel[currentSecurity]);
100
101// Union types - one of several types
102type DinosaurDiet = "carnivore" | "herbivore" | "omnivore";
103let trexDiet: DinosaurDiet = "carnivore";
104
105// Literal types
106type DinosaurPeriod = "Triassic" | "Jurassic" | "Cretaceous";
107let period: DinosaurPeriod = "Cretaceous";
108
109console.log("Basic TypeScript types defined!\n");
110
111// ===========================================
112// 4. Interfaces and object types
113// ===========================================
114console.log("=== 4. INTERFACES AND OBJECT TYPES ===");
115
116// Interface for a dinosaur
117interface Dinosaur {
118  readonly id: string; // Read-only
119  name: string;
120  species: string;
121  diet: DinosaurDiet;
122  weight: number;
123  dangerLevel: number;
124  isActive?: boolean; // Optional
125
126  // Methods in the interface
127  roar(): string;
128  feed(food: string): void;
129}
130
131// Implementing the interface
132class TyrannosaurusRex implements Dinosaur {
133  readonly id: string;
134  name: string;
135  species: string;
136  diet: DinosaurDiet;
137  weight: number;
138  dangerLevel: number;
139  isActive: boolean;
140
141  constructor(name: string) {
142    this.id = `DINO-${Date.now()}`;
143    this.name = name;
144    this.species = "Tyrannosaurus Rex";
145    this.diet = "carnivore";
146    this.weight = 8000;
147    this.dangerLevel = 9;
148    this.isActive = true;
149  }
150
151  roar(): string {
152    return `${this.name} roars: ROAAAAAR!`;
153  }
154
155  feed(food: string): void {
156    console.log(`${this.name} eats ${food}`);
157  }
158
159  hunt(prey: string): string {
160    return `${this.name} hunts ${prey}`;
161  }
162}
163
164const rexy = new TyrannosaurusRex("Rexy");
165console.log("Dinosaur created:", rexy.name);
166console.log("Roar:", rexy.roar());
167rexy.feed("goat");
168
169// Extending interfaces
170interface CarnivorousDinosaur extends Dinosaur {
171  huntingStyle: string;
172  packSize: number;
173  hunt(prey: string): string;
174}
175
176// Type aliases for complex types
177type DinosaurCollection = {
178  [key: string]: Dinosaur;
179};
180
181type EnclosureStatus = "secure" | "breached" | "maintenance";
182
183console.log("Interfaces and object types defined!\n");
184
185// ===========================================
186// 5. Generics - parametric types
187// ===========================================
188console.log("=== 5. GENERICS - PARAMETRIC TYPES ===");
189
190// Generic function
191function createContainer<T>(item: T): { content: T; timestamp: Date } {
192  return {
193    content: item,
194    timestamp: new Date()
195  };
196}
197
198// Usage with different types
199const dinoContainer = createContainer<Dinosaur>(rexy);
200const stringContainer = createContainer<string>("Important data");
201const numberContainer = createContainer<number>(42);
202
203console.log("Dino container:", dinoContainer.content.name);
204console.log("String container:", stringContainer.content);
205
206// Generic interface
207interface Repository<T> {
208  items: T[];
209  add(item: T): void;
210  find(predicate: (item: T) => boolean): T | undefined;
211  getAll(): T[];
212}
213
214// Implementation for dinosaurs
215class DinosaurRepository implements Repository<Dinosaur> {
216  items: Dinosaur[] = [];
217
218  add(dinosaur: Dinosaur): void {
219    this.items.push(dinosaur);
220    console.log(`Added ${dinosaur.name} to repository`);
221  }
222
223  find(predicate: (dinosaur: Dinosaur) => boolean): Dinosaur | undefined {
224    return this.items.find(predicate);
225  }
226
227  getAll(): Dinosaur[] {
228    return [...this.items];
229  }
230
231  findByDiet(diet: DinosaurDiet): Dinosaur[] {
232    return this.items.filter(dino => dino.diet === diet);
233  }
234}
235
236const dinoRepo = new DinosaurRepository();
237dinoRepo.add(rexy);
238
239// Generic constraints
240interface HasName {
241  name: string;
242}
243
244function logName<T extends HasName>(item: T): T {
245  console.log(`Logging: ${item.name}`);
246  return item;
247}
248
249logName(rexy); // Works - Dinosaur has name
250// logName(123); // Error - number doesn't have name
251
252console.log("Generics work!\n");
253
254// ===========================================
255// 6. Utility Types - built-in helper types
256// ===========================================
257console.log("=== 6. UTILITY TYPES ===");
258
259// Partial - all fields optional
260type PartialDinosaur = Partial<Dinosaur>;
261
262function updateDinosaur(dino: Dinosaur, updates: PartialDinosaur): Dinosaur {
263  return { ...dino, ...updates };
264}
265
266const updatedRexy = updateDinosaur(rexy, { isActive: false });
267console.log(`${updatedRexy.name} is active: ${updatedRexy.isActive}`);
268
269// Pick - selecting specific fields
270type DinosaurSummary = Pick<Dinosaur, "name" | "species" | "dangerLevel">;
271
272function createSummary(dino: Dinosaur): DinosaurSummary {
273  return {
274    name: dino.name,
275    species: dino.species,
276    dangerLevel: dino.dangerLevel
277  };
278}
279
280const summary = createSummary(rexy);
281console.log("Dinosaur summary:", summary);
282
283// Omit - removing specific fields
284type PublicDinosaurInfo = Omit<Dinosaur, "id" | "dangerLevel">;
285
286function getPublicInfo(dino: Dinosaur): PublicDinosaurInfo {
287  const { id, dangerLevel, ...publicInfo } = dino;
288  return publicInfo;
289}
290
291// Record - mapping keys to values
292type DinosaurStats = Record<DinosaurDiet, number>;
293
294const dietStats: DinosaurStats = {
295  carnivore: 3,
296  herbivore: 7,
297  omnivore: 2
298};
299
300console.log("Diet statistics:", dietStats);
301
302// Required - all fields required
303type RequiredDinosaur = Required<Dinosaur>;
304
305console.log("Utility types work!\n");
306
307// ===========================================
308// 7. Advanced types
309// ===========================================
310console.log("=== 7. ADVANCED TYPES ===");
311
312// Mapped types
313type ReadonlyDinosaur = {
314  readonly [K in keyof Dinosaur]: Dinosaur[K];
315};
316
317// Conditional types
318type DinosaurSound<T> = T extends "carnivore" ? "ROAR" : "HONK";
319
320type TRexSound = DinosaurSound<"carnivore">; // "ROAR"
321type TriceratopsSound = DinosaurSound<"herbivore">; // "HONK"
322
323// Template literal types
324type EventName<T extends string> = `dinosaur-${T}`;
325type DinosaurEvents = EventName<"fed" | "escaped" | "sleeping">;
326
327// Function types
328type DinosaurEventHandler = (dinosaur: Dinosaur, event: DinosaurEvents) => void;
329
330const eventHandler: DinosaurEventHandler = (dino, event) => {
331  console.log(`Event ${event} for ${dino.name}`);
332};
333
334eventHandler(rexy, "dinosaur-fed");
335
336console.log("Advanced types work!\n");
337
338// ===========================================
339// 8. Practical example - Park management system
340// ===========================================
341console.log("=== 8. PARK MANAGEMENT SYSTEM ===");
342
343// A complex type system for the park
344interface Enclosure {
345  id: string;
346  name: string;
347  capacity: number;
348  type: "carnivore" | "herbivore" | "mixed";
349  securityLevel: SecurityLevel;
350  dinosaurs: Dinosaur[];
351  isLocked: boolean;
352}
353
354interface ParkEvent {
355  id: string;
356  timestamp: Date;
357  type: "feeding" | "escape" | "maintenance" | "visitor";
358  entityId: string;
359  severity: "low" | "medium" | "high" | "critical";
360  message: string;
361}
362
363class ParkManager {
364  private enclosures: Map<string, Enclosure> = new Map();
365  private eventLog: ParkEvent[] = [];
366
367  addEnclosure(enclosure: Omit<Enclosure, "id">): string {
368    const id = `ENC-${Date.now()}`;
369    const newEnclosure: Enclosure = { ...enclosure, id };
370    this.enclosures.set(id, newEnclosure);
371
372    this.logEvent({
373      type: "maintenance",
374      entityId: id,
375      severity: "low",
376      message: `Enclosure ${enclosure.name} created`
377    });
378
379    return id;
380  }
381
382  addDinosaurToEnclosure(dinosaurId: string, enclosureId: string): boolean {
383    const enclosure = this.enclosures.get(enclosureId);
384    if (!enclosure) {
385      console.log("Enclosure not found");
386      return false;
387    }
388
389    if (enclosure.dinosaurs.length >= enclosure.capacity) {
390      console.log("Enclosure at capacity");
391      return false;
392    }
393
394    // In a real application we would fetch the dinosaur from the repository
395    console.log(`Dinosaur ${dinosaurId} added to ${enclosure.name}`);
396    return true;
397  }
398
399  private logEvent(event: Omit<ParkEvent, "id" | "timestamp">): void {
400    const newEvent: ParkEvent = {
401      ...event,
402      id: `EVT-${Date.now()}`,
403      timestamp: new Date()
404    };
405    this.eventLog.push(newEvent);
406  }
407
408  getEventsByType<T extends ParkEvent["type"]>(type: T): ParkEvent[] {
409    return this.eventLog.filter(event => event.type === type);
410  }
411
412  getParkStatus(): {
413    enclosureCount: number;
414    totalDinosaurs: number;
415    securityLevel: SecurityLevel;
416    recentEvents: ParkEvent[];
417  } {
418    const enclosures = Array.from(this.enclosures.values());
419    const totalDinosaurs = enclosures.reduce((sum, enc) => sum + enc.dinosaurs.length, 0);
420    const maxSecurityLevel = Math.max(...enclosures.map(enc => enc.securityLevel));
421
422    return {
423      enclosureCount: enclosures.length,
424      totalDinosaurs,
425      securityLevel: maxSecurityLevel as SecurityLevel,
426      recentEvents: this.eventLog.slice(-5)
427    };
428  }
429}
430
431// System test
432const parkManager = new ParkManager();
433
434const enclosureId = parkManager.addEnclosure({
435  name: "T-Rex Paddock",
436  capacity: 2,
437  type: "carnivore",
438  securityLevel: SecurityLevel.Critical,
439  dinosaurs: [],
440  isLocked: true
441});
442
443parkManager.addDinosaurToEnclosure("rexy", enclosureId);
444
445const status = parkManager.getParkStatus();
446console.log("Park status:", status);
447
448console.log("\nTypeScript Park Management System works!\n");
449
450// ===========================================
451// 9. TypeScript benefits
452// ===========================================
453console.log("=== 9. TYPESCRIPT BENEFITS ===");
454
455console.log("TYPE SAFETY:");
456console.log("  • Error detection at compile-time");
457console.log("  • Preventing common JavaScript errors");
458console.log("  • Certainty about data structure\n");
459
460console.log("BETTER TOOLS:");
461console.log("  • IntelliSense/autocomplete");
462console.log("  • Confident refactoring in the IDE");
463console.log("  • Automatic syntax checking\n");
464
465console.log("CODE DOCUMENTATION:");
466console.log("  • Types serve as documentation");
467console.log("  • Clear API interfaces");
468console.log("  • More readable code for the team\n");
469
470console.log("SCALABILITY:");
471console.log("  • Easier management of large projects");
472console.log("  • Better team collaboration");
473console.log("  • Lower risk of regressions\n");
474
475console.log("COMPATIBILITY WITH JAVASCRIPT:");
476console.log("  • Gradual migration from JS");
477console.log("  • Using JavaScript libraries");
478console.log("  • Compiles to plain JS\n");
479
480console.log("TypeScript - safer JavaScript for Jurassic Park!");

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What is the most important advantage of TypeScript compared to JavaScript?

  2. 2. What is the relationship between TypeScript and JavaScript?

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Code editor

    Define a variable dinosaurSpecies of type string and assign it the value 'T-Rex'.

Useful articles