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=== nullchecks and knows which exact type you are working with in each branch - Discriminated unions — a shared literal
typefield lets TypeScript narrow to the exact variant automatically - In Astro: TypeScript is already available — rename
.jsxto.tsxand 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.