Introduction to TypeScript
TypeScript is a strongly typed superset of JavaScript that compiles to plain JavaScript. It adds a static type system, modern ECMAScript features, and tooling that helps teams build large, reliable applications.
Why TypeScript is valuable:
- Early error detection at compile time
- Safer refactoring with IDE support and type-aware navigation
- Clear contracts via interfaces and types
- Incremental adoption in existing JavaScript projects
TypeScript Basics
Setup & Compilation
npm install -g typescript
tsc app.ts # Compile
tsc --watch app.ts # Auto compile on save
For real projects, configure TypeScript with tsconfig.json so the compiler enforces consistent rules across files.
Types
let isDone: boolean = false;
let age: number = 25;
let name: string = "Nafees";
let arr: number[] = [1,2,3];
let tuple: [string, number] = ["RollNo", 101];
let anything: any = "Hello";
Prefer unknown over any for safer handling of dynamic values.
Type Inference
let message = "Hello"; // inferred as string
message = 123; // ❌ Error
Type inference reduces verbosity while still providing strong type checks. Use explicit types for public APIs and complex data structures.
Functions in TypeScript
Typed Functions
function add(a: number, b: number): number {
return a + b;
}
const multiply = (x: number, y: number): number => x * y;
Function types can be declared explicitly to enforce parameter and return shapes.
Optional & Default Parameters
function greet(name: string, greeting?: string){
console.log(`${greeting || "Hello"} ${name}`);
}
greet("Nafees"); // Hello Nafees
greet("Nafees","Hi"); // Hi Nafees
Optional parameters must come after required ones. Default values provide safer function calls.
Rest Parameters
function sum(...nums: number[]): number {
return nums.reduce((a,b)=>a+b, 0);
}
sum(1,2,3,4); // 10
Rest parameters are typed arrays and work well with generics for flexible utilities.
Classes & Interfaces
Classes
class Person {
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
greet(): void {
console.log(`Hello ${this.name}, age ${this.age}`);
}
}
const p = new Person("Nafees",25);
p.greet();
TypeScript supports access modifiers (public, private, protected) and readonly properties for stronger encapsulation.
Interfaces
interface Employee {
id: number;
name: string;
salary?: number; // optional
}
function printEmployee(emp: Employee){
console.log(emp.name);
}
printEmployee({id:1,name:"Ali"});
Interfaces describe shapes of objects, function signatures, and class contracts. They are extendable and can be merged across declarations.
Advanced Types
Union & Intersection Types
let value: string | number;
value = "Hello";
value = 100;
interface A { a: number; }
interface B { b: string; }
type C = A & B; // Intersection type
Union types allow flexibility while preserving type safety. Intersections combine multiple type requirements.
Enums
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
Enums create named constants. Prefer const enum for performance or union literals for smaller bundles.
Generics
function identity(arg: T): T { return arg; } let output = identity ("Hello"); function getArray (items:T[]): T[] { return new Array().concat(items); }
Generics provide reusable, type-safe functions and classes without losing type information.
Type Aliases & Literal Types
type ID = number | string;
let userId: ID = 101;
let direction: "up" | "down" | "left" | "right";
direction = "up"; // valid
Literal types and aliases help model strict sets of values and improve autocomplete.
Modules & Namespaces
Modules
// math.ts
export function add(a:number,b:number):number { return a+b; }
// app.ts
import { add } from './math';
console.log(add(2,3));
Modules are the recommended approach for modern TypeScript projects. They align with bundlers and tree-shaking.
Namespaces
namespace Utility {
export function log(msg:string){ console.log(msg); }
}
Utility.log("Hello");
Namespaces are mostly used for legacy patterns. Prefer modules for scalability.
TypeScript with DOM
let button = document.getElementById("btn") as HTMLButtonElement;
button.addEventListener("click", ()=>{ alert("Button Clicked!"); });
let input = document.querySelector("input") as HTMLInputElement;
console.log(input.value);
TypeScript can narrow DOM types with type assertions and guards. Always handle possible null values when querying elements.
const button = document.getElementById("btn");
if (button instanceof HTMLButtonElement) {
button.disabled = false;
}
Async Programming
Promises
function fetchData(): Promise{ return new Promise(resolve => { setTimeout(()=> resolve("Data received"), 1000); }); } fetchData().then(console.log);
Promises model asynchronous results. Use Promise.all for parallel tasks and handle errors with catch.
Async/Await
async function getData(){
const result = await fetchData();
console.log(result);
}
getData();
Async/await provides cleaner control flow and easier error handling with try/catch.