Academy
ProgrammingJS AdvancedTypeScript Essentials

TypeScript Essentials

What TypeScript Actually Is

TypeScript is JavaScript with a type-checker bolted on top. Every valid JavaScript file is a valid TypeScript file. TypeScript adds zero runtime overhead — it compiles away entirely, leaving plain JavaScript. What it adds is a static analysis pass that runs before your code executes, catching type errors at the editor level rather than in production.

The TypeScript compiler cannot run in this sandbox — it needs to be installed. But the concepts TypeScript enforces are pure JavaScript ideas. Understanding what TypeScript checks makes the syntax obvious when you see it. We will run the JavaScript equivalent of every TypeScript concept, then show what it looks like in real TypeScript.

The Problem TypeScript Solves

In TypeScript the function signature would be:

interface Student {
  name: string;
  marks: number;
}

function gradeStudent(student: Student): string {
  return student.name.toUpperCase() + ": " + student.marks;
}

gradeStudent({ name: "Priya" }); // TS error: marks is missing
gradeStudent({ name: null, marks: 91 }); // TS error: null not assignable to string
gradeStudent(null); // TS error: null not assignable to Student
gradeStudent({ nme: "Arjun", marks: 49 }); // TS error: nme does not exist on Student

The compiler finds all four bugs before the code runs. In a large codebase this is the difference between discovering a bug in a code review and discovering it in a customer complaint.

Type Annotations — The Syntax

TypeScript adds type annotations with a : Type suffix. They are purely declarative — they cost nothing at runtime:

// Primitives
const name: string = "Rahul";
const marks: number = 82;
const passed: boolean = true;

// Arrays
const scores: number[] = [82, 91, 49];
const names: Array<string> = ["Rahul", "Priya"];

// Functions — parameter types and return type
function add(a: number, b: number): number {
  return a + b;
}

// Optional parameters
function greet(name: string, title?: string): string {
  return title ? `${title} ${name}` : name;
}

// Union types — one of several types
function format(value: string | number): string {
  return String(value);
}

// Literal types — exactly one of these values
type Status = "pass" | "fail" | "pending";

Interfaces and Type Aliases

An interface describes the shape of an object. A type alias names any type expression. Both are erased at compile time:

interface Student {
  id: number;
  name: string;
  marks: number;
  email?: string; // optional
}

// Extending interfaces
interface EnrolledStudent extends Student {
  courseId: string;
  enrolDate: Date;
}

// Type alias — can name any type, not just objects
type Grade = "A+" | "A" | "B" | "F";
type Students = Student[];

// Intersection — combine multiple types
type AdminStudent = Student & { adminNotes: string };

Generics — Types as Parameters

Generics let you write functions and classes that work on any type while preserving type information through the call:

// Without generics — loses type information
function first(arr: any[]): any {
  return arr[0];
}
const x = first([1, 2, 3]); // x is 'any' — type lost

// With generics — TypeScript infers T at each call site
function first<T>(arr: T[]): T {
  return arr[0];
}
const n = first([1, 2, 3]); // n is number
const s = first(["a", "b"]); // s is string

// Generic constraint — T must have a marks property
function getMarks<T extends { marks: number }>(item: T): number {
  return item.marks;
}

// Generic class — Stack that only accepts one type
class Stack<T> {
  private items: T[] = [];
  push(item: T): void {
    this.items.push(item);
  }
  pop(): T | undefined {
    return this.items.pop();
  }
  get size(): number {
    return this.items.length;
  }
}

const nums = new Stack<number>(); // only accepts numbers
nums.push(42);
nums.push("hello"); // TS error: string not assignable to number

Utility Types — TypeScript's Built-in Transformers

TypeScript ships with utility types that transform existing types into new ones:

interface Student {
  id:    number;
  name:  string;
  marks: number;
  email: string;
}

// Partial<T> — all properties become optional (update operations)
type StudentUpdate = Partial<Student>;

// Pick<T, K> — keep only certain properties
type StudentSummary = Pick<Student, "name" | "marks">;
// { name: string; marks: number }

// Omit<T, K> — remove certain properties
type PublicStudent = Omit<Student, "email">;

// Record<K, V> — keyed object
type SubjectScores = Record<string, number>;

// Readonly<T> — all properties immutable
type ImmutableStudent = Readonly<Student>;

// ReturnType<T> — extract a function's return type
function makeStudent(name: string): Student { ... }
type S = ReturnType<typeof makeStudent>; // Student

Type Narrowing — TypeScript Follows Your Logic

TypeScript tracks which type a variable must be at each point, based on checks you have already written:

function process(value: string | number | null): void {
  if (value === null) {
    // TypeScript knows: null here
    return;
  }
  if (typeof value === "string") {
    // TypeScript knows: string here
    console.log(value.toUpperCase());
    return;
  }
  // TypeScript knows: must be number — null and string eliminated
  console.log(value.toFixed(2));
}

Setting Up TypeScript

In a real project, TypeScript is three commands:

npm install -D typescript
npx tsc --init     # creates tsconfig.json
npx tsc            # compile all .ts files to .js

For your Astro site, TypeScript is already configured — Astro ships with TypeScript support built in. Any .ts or .tsx file is compiled automatically. Your existing .jsx components become .tsx with annotations added incrementally:

// JSRunner.tsx — typed version of your existing component
interface JSRunnerProps {
  initialCode?: string;
}

export default function JSRunner({
  initialCode = DEFAULT_CODE,
}: JSRunnerProps) {
  const [code, setCode] = useState<string>(initialCode);
  const [output, setOutput] = useState<OutputLine[]>([]);
  const [hasError, setHasError] = useState<boolean>(false);
  // rest unchanged
}

Rename one file, fix what the compiler surfaces, move on. The migration is incremental — you never need a big-bang rewrite.

What You Have Learned

TypeScript is not a different language. It is JavaScript with a correctness verification layer that runs before execution.

The key ideas:

  • TypeScript compiles away entirely — zero runtime overhead, pure JavaScript output
  • Type annotations (: string, : number, : Student) declare what a value must be — errors shown in your editor as you type
  • Interfaces describe object shapes; type aliases name any type — both erased at compile time
  • Generics (<T>) let functions and classes work on any type while preserving type information at each call site
  • Utility types (Partial, Pick, Omit, Record, Readonly) transform existing types into new ones — the same patterns you already write with spread and filter
  • Type narrowing — TypeScript follows your typeof, instanceof, and === null checks and knows which exact type you are working with in each branch
  • Discriminated unions — a shared literal type field lets TypeScript narrow to the exact variant automatically
  • In Astro: TypeScript is already available — rename .jsx to .tsx and add annotations incrementally

In the next lesson: Advanced TypeScript — conditional types, mapped types, template literal types, infer, and the type-level programming that powers the type definitions of every major library.