TypeScript Basics Tutorial

Learn TypeScript type system, interfaces, generics, and core concepts from scratch

TypeScript Basics Tutorial

TypeScript is a superset of JavaScript that adds static type checking. This article covers TypeScript fundamentals.

Why Use TypeScript

Key Benefits

TypeScript Benefits:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Developer Experience                              │
│   ├── Intelligent code completion                  │
│   ├── Real-time error detection                    │
│   └── Better refactoring support                   │
│                                                     │
│   Code Quality                                      │
│   ├── Catch runtime errors at compile time         │
│   ├── Code as documentation                        │
│   └── More maintainable large projects             │
│                                                     │
│   Team Collaboration                                │
│   ├── Clear interface definitions                  │
│   ├── Easier to understand others' code            │
│   └── Automatic API change detection               │
│                                                     │
└─────────────────────────────────────────────────────┘

Quick Start

# Install TypeScript
npm install -g typescript

# Compile TypeScript file
tsc hello.ts

# Initialize project
tsc --init

# Watch mode
tsc --watch

Basic Types

Primitive Types

// Boolean
let isDone: boolean = false;

// Number
let count: number = 42;
let hex: number = 0xf00d;
let binary: number = 0b1010;

// String
let name: string = 'Alice';
let greeting: string = `Hello, ${name}`;

// null and undefined
let n: null = null;
let u: undefined = undefined;

// Symbol
let sym: symbol = Symbol('key');

// BigInt
let big: bigint = 100n;

Arrays

// Array types
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ['Alice', 'Bob'];

// Readonly arrays
let readonly: readonly number[] = [1, 2, 3];
let readonlyAlt: ReadonlyArray<number> = [1, 2, 3];

// Mixed type arrays
let mixed: (string | number)[] = [1, 'two', 3];

Tuples

// Tuple: fixed length and type array
let tuple: [string, number] = ['Alice', 25];

// Access elements
const name = tuple[0];  // string
const age = tuple[1];   // number

// Optional elements
let optional: [string, number?] = ['Alice'];

// Rest elements
let rest: [string, ...number[]] = ['sum', 1, 2, 3];

// Named tuples
let person: [name: string, age: number] = ['Alice', 25];

Enums

// Numeric enum
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right    // 3
}

// Custom starting value
enum Status {
  Pending = 1,
  Active,     // 2
  Completed   // 3
}

// String enum
enum Color {
  Red = '#FF0000',
  Green = '#00FF00',
  Blue = '#0000FF'
}

// Using enums
let dir: Direction = Direction.Up;

// Const enum (inlined at compile time)
const enum Size {
  Small = 1,
  Medium = 2,
  Large = 3
}

Special Types

// any - any type (avoid using)
let anything: any = 'hello';
anything = 42;
anything = true;

// unknown - safe any
let unknown: unknown = 'hello';
// unknown.toUpperCase();  // Error
if (typeof unknown === 'string') {
  unknown.toUpperCase();  // OK
}

// void - no return value
function log(message: string): void {
  console.log(message);
}

// never - never returns
function error(message: string): never {
  throw new Error(message);
}

function infinite(): never {
  while (true) {}
}

// object - non-primitive type
let obj: object = { name: 'Alice' };

Type Annotations and Inference

Type Annotations

// Variable annotations
let name: string = 'Alice';
let age: number = 25;

// Function parameters and return
function add(a: number, b: number): number {
  return a + b;
}

// Optional parameters
function greet(name: string, greeting?: string): string {
  return `${greeting || 'Hello'}, ${name}`;
}

// Default parameters
function greet2(name: string, greeting: string = 'Hello'): string {
  return `${greeting}, ${name}`;
}

// Rest parameters
function sum(...numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0);
}

Type Inference

// TypeScript infers types automatically
let name = 'Alice';      // Inferred as string
let age = 25;            // Inferred as number
let active = true;       // Inferred as boolean

// Array inference
let numbers = [1, 2, 3];        // number[]
let mixed = [1, 'two'];         // (string | number)[]

// Function return inference
function add(a: number, b: number) {
  return a + b;  // Inferred return number
}

// Contextual type inference
const names = ['Alice', 'Bob'];
names.forEach(name => {
  console.log(name.toUpperCase());  // name inferred as string
});

Interfaces

Basic Interfaces

// Define interface
interface User {
  name: string;
  age: number;
}

// Use interface
const user: User = {
  name: 'Alice',
  age: 25
};

// Optional properties
interface Config {
  host: string;
  port?: number;
}

// Readonly properties
interface Point {
  readonly x: number;
  readonly y: number;
}

const point: Point = { x: 10, y: 20 };
// point.x = 20;  // Error

Interface Extension

// Interface inheritance
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

const dog: Dog = {
  name: 'Buddy',
  breed: 'Labrador'
};

// Multiple inheritance
interface Swimmer {
  swim(): void;
}

interface Runner {
  run(): void;
}

interface Athlete extends Swimmer, Runner {
  compete(): void;
}

Function Interfaces

// Function type interface
interface MathFunc {
  (a: number, b: number): number;
}

const add: MathFunc = (a, b) => a + b;
const multiply: MathFunc = (a, b) => a * b;

// Callable interface
interface Greeting {
  (name: string): string;
  language: string;
}

const greet: Greeting = (name) => `Hello, ${name}`;
greet.language = 'en';

Index Signatures

// String index
interface StringMap {
  [key: string]: string;
}

const colors: StringMap = {
  red: '#FF0000',
  green: '#00FF00'
};

// Number index
interface NumberArray {
  [index: number]: string;
}

const arr: NumberArray = ['a', 'b', 'c'];

// Mixed index
interface Dictionary {
  [key: string]: number | string;
  length: number;
  name: string;
}

Type Aliases

Basic Usage

// Type alias
type ID = string | number;
type Name = string;

let userId: ID = 123;
userId = 'abc123';

// Object type
type User = {
  name: string;
  age: number;
};

// Function type
type Callback = (data: string) => void;

// Tuple type
type Point = [number, number];

Type Alias vs Interface

// Interface can extend
interface Animal {
  name: string;
}
interface Dog extends Animal {
  breed: string;
}

// Type alias uses intersection
type Animal2 = {
  name: string;
};
type Dog2 = Animal2 & {
  breed: string;
};

// Interface can merge declarations
interface User {
  name: string;
}
interface User {
  age: number;
}
// User now has name and age

// Type alias cannot be redeclared
// type User = { name: string };
// type User = { age: number };  // Error

Union and Intersection Types

Union Types

// Union type: can be one of multiple types
type ID = string | number;

function printId(id: ID) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

// Literal unions
type Direction = 'up' | 'down' | 'left' | 'right';
type Status = 'pending' | 'active' | 'completed';

function move(direction: Direction) {
  console.log(`Moving ${direction}`);
}

move('up');
// move('diagonal');  // Error

Intersection Types

// Intersection type: combine multiple types
type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: number;
  department: string;
};

type Staff = Person & Employee;

const staff: Staff = {
  name: 'Alice',
  age: 25,
  employeeId: 1001,
  department: 'Engineering'
};

Type Narrowing

// typeof narrowing
function process(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  return value * 2;
}

// instanceof narrowing
function processDate(value: Date | string) {
  if (value instanceof Date) {
    return value.getTime();
  }
  return new Date(value).getTime();
}

// in operator narrowing
interface Cat { meow(): void; }
interface Dog { bark(): void; }

function speak(animal: Cat | Dog) {
  if ('meow' in animal) {
    animal.meow();
  } else {
    animal.bark();
  }
}

// Custom type guard
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function process2(value: unknown) {
  if (isString(value)) {
    console.log(value.toUpperCase());
  }
}

Generics

Generic Functions

// Generic function
function identity<T>(arg: T): T {
  return arg;
}

// Usage
identity<string>('hello');  // Explicit type
identity(42);               // Type inference

// Multiple type parameters
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

pair<string, number>('age', 25);

Generic Interfaces

// Generic interface
interface Container<T> {
  value: T;
  getValue(): T;
}

const numberContainer: Container<number> = {
  value: 42,
  getValue() { return this.value; }
};

// Generic array
interface List<T> {
  items: T[];
  add(item: T): void;
  get(index: number): T;
}

Generic Constraints

// Basic constraint
function getLength<T extends { length: number }>(arg: T): number {
  return arg.length;
}

getLength('hello');     // OK
getLength([1, 2, 3]);   // OK
// getLength(123);      // Error

// keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: 'Alice', age: 25 };
getProperty(user, 'name');  // string
getProperty(user, 'age');   // number
// getProperty(user, 'email');  // Error

// Default type parameters
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

Classes

Basic Classes

class Person {
  // Properties
  name: string;
  age: number;

  // Constructor
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // Method
  greet(): string {
    return `Hello, I'm ${this.name}`;
  }
}

const person = new Person('Alice', 25);

Access Modifiers

class Employee {
  public name: string;       // Public (default)
  private salary: number;    // Private
  protected id: number;      // Protected
  readonly department: string;  // Readonly

  constructor(
    name: string,
    salary: number,
    id: number,
    department: string
  ) {
    this.name = name;
    this.salary = salary;
    this.id = id;
    this.department = department;
  }
}

// Parameter properties shorthand
class User {
  constructor(
    public name: string,
    private email: string,
    readonly id: number
  ) {}
}

Inheritance

class Animal {
  constructor(public name: string) {}

  speak(): void {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name);
  }

  speak(): void {
    console.log(`${this.name} barks`);
  }

  fetch(): void {
    console.log(`${this.name} fetches the ball`);
  }
}

Abstract Classes

abstract class Shape {
  abstract area(): number;
  abstract perimeter(): number;

  describe(): string {
    return `Area: ${this.area()}, Perimeter: ${this.perimeter()}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }

  area(): number {
    return Math.PI * this.radius ** 2;
  }

  perimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}

Implementing Interfaces

interface Printable {
  print(): void;
}

interface Saveable {
  save(): void;
}

class Document implements Printable, Saveable {
  constructor(private content: string) {}

  print(): void {
    console.log(this.content);
  }

  save(): void {
    console.log('Saving document...');
  }
}

Modules

Exports

// Named exports
export const PI = 3.14159;
export function add(a: number, b: number): number {
  return a + b;
}
export interface User {
  name: string;
}

// Default export
export default class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }
}

// Re-export
export { PI as PI_VALUE } from './constants';
export * from './helpers';

Imports

// Named imports
import { PI, add, User } from './math';

// Default import
import Calculator from './calculator';

// Mixed imports
import Calculator, { PI, add } from './math';

// Aliased imports
import { add as addNumbers } from './math';

// Namespace imports
import * as math from './math';
math.add(1, 2);

// Type imports
import type { User } from './types';

Best Practices Summary

TypeScript Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Type Usage                                        │
│   ├── Avoid any, use unknown instead               │
│   ├── Prefer type inference                        │
│   ├── Use strict mode (strict: true)               │
│   └── Explicitly declare types for public APIs     │
│                                                     │
│   Interface vs Type Alias                           │
│   ├── Use interface for object shapes              │
│   ├── Use type alias for unions/tuples             │
│   └── Use interface when extension needed          │
│                                                     │
│   Code Organization                                 │
│   ├── Keep related types together                  │
│   ├── Organize code with modules                   │
│   └── Separate type definition files (.d.ts)       │
│                                                     │
└─────────────────────────────────────────────────────┘
ConceptPurposeExample
InterfaceDefine object shapeinterface User { name: string }
Type AliasCreate type namestype ID = string | number
GenericsReusable type logicfunction identity<T>(x: T): T
Union TypeMultiple optionsstring | number

Master TypeScript basics to write more reliable JavaScript code.