Skip to content
Home
TypeScript Basics: Types, Functions, Classes, and Enums Explained

TypeScript Basics: Types, Functions, Classes, and Enums Explained

TypeScript TypeScript 7 min read 1460 words Beginner ExcellentWiki Editorial Team

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Developed by Microsoft and first released in 2012, it adds static type checking, interfaces, generics, and modern ECMAScript features to the JavaScript ecosystem. As of 2025, TypeScript is used by over 90% of enterprise JavaScript projects and powers frameworks like Angular, Next.js, and NestJS (see State of JS 2024 Survey). This guide covers the fundamentals: primitive types, function types, object types, classes, enums, and the practical setup needed to start building.

Why Start with TypeScript?

If you are new to typed JavaScript, the benefits may not be immediately obvious. Over a decade of industry adoption has demonstrated that TypeScript:

  • Reduces runtime errors: The University of Cambridge found that TypeScript catches 15-25% of production JavaScript bugs during development.
  • Improves developer experience: IDEs provide accurate autocomplete, inline documentation, and instant refactoring tools when they understand your types.
  • Documents your intent: Function signatures serve as executable documentation. A typed function findUser(id: string): Promise<User | null> communicates its contract without needing a comment.
  • Enables safe refactoring: Rename a property or change a function signature, and the compiler finds every affected call site instantly.

Primitive Types

TypeScript’s type system starts with the same primitives as JavaScript: string, number, boolean, null, undefined, symbol, and bigint. Type annotations follow a colon after the variable name.

let name: string = 'ExcellentWiki';
let year: number = 2026;
let isActive: boolean = true;

TypeScript infers types when declarations include an initial value. Idiomatic TypeScript omits annotations where inference is unambiguous:

let name = 'ExcellentWiki'; // inferred as string

The any type disables type checking for a value. It should be avoided unless migrating JavaScript incrementally. The unknown type is the type-safe counterpart — you must narrow it before use.

Union and Intersection Types

Union types (|) model values that can be one of several types:

type Id = string | number;
function lookup(id: Id) {
  if (typeof id === 'string') return fetchByName(id);
  return fetchByNumber(id);
---

Intersection types (&) combine multiple types into one:

type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // { name: string; age: number; }

Union types are essential for modeling API responses, configuration options, and state machines. TypeScript’s control flow analysis narrows unions automatically based on checks like typeof, instanceof, and property presence.

Type Aliases

Type aliases name any type expression, including unions, intersections, and primitives:

type Point = { x: number; y: number };
type Status = 'active' | 'inactive' | 'pending';
type Callback<T> = (err: Error | null, result?: T) => void;

Unlike interfaces, type aliases cannot be reopened or extended with extends, but they can express unions, tuples, and conditional types — making them indispensable for utility and transformation code.

Arrays and Tuples

Arrays are typed with T[] or Array<T>:

let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];

Tuples are fixed-length arrays with typed positions:

let pair: [string, number] = ['Alice', 42];

TypeScript enforces tuple length at compile time, preventing out-of-bounds access that would be valid for regular arrays.

Function Types

Functions specify parameter types and return types. TypeScript infers the return type from the function body.

function add(a: number, b: number): number {
  return a + b;
---

Optional and Default Parameters

Optional parameters use ?:

function greet(name: string, greeting?: string): string {
  return `${greeting ?? 'Hello'}, ${name}!`;
---

Default parameters follow JavaScript syntax but benefit from type inference:

function multiply(a: number, b: number = 1): number {
  return a * b;
---

Function Type Expressions

Functions can be typed as values using arrow syntax:

type MathOp = (x: number, y: number) => number;
const add: MathOp = (a, b) => a + b;

Function overloads let you model polymorphic behavior with precise return types:

function reverse(items: string): string;
function reverse<T>(items: T[]): T[];
function reverse(items: any): any {
  if (typeof items === 'string') return items.split('').reverse().join('');
  return items.reverse();
---

Object Types and Interfaces

Object types describe the shape of a value. You can define them inline or with interface:

interface User {
  id: number;
  name: string;
  email?: string; // optional
  readonly createdAt: Date; // immutable after creation
---

Interfaces support optional properties, readonly modifiers, and inheritance via extends. They also participate in declaration merging (see the Types vs Interfaces guide for detailed comparison).

Index Signatures

For dynamic property access, use index signatures:

interface Dictionary<T> {
  [key: string]: T;
---

Classes in TypeScript

TypeScript extends ES2015+ classes with access modifiers (public, private, protected), parameter properties, and abstract classes.

class Animal {
  constructor(public name: string, private age: number) {}
  
  public speak(): void {
    console.log(`${this.name} speaks.`);
  }
---

Parameter properties (public name: string in the constructor) simultaneously declare and initialize a property. This reduces boilerplate compared to JavaScript classes.

Access Modifiers

  • public (default): Accessible anywhere.
  • private: Only accessible within the declaring class. TypeScript enforces this at compile time, but JavaScript runtimes may still allow access.
  • protected: Accessible within the class and subclasses.
  • readonly: Prevents property reassignment after initialization.

TypeScript also supports JavaScript’s native #private syntax (hard private), which is enforced at runtime:

class Wallet {
  #balance: number = 0;  // true runtime privacy

  deposit(amount: number): void {
    this.#balance += amount;
  }
---

Abstract Classes

Abstract classes cannot be instantiated directly; they define contracts for subclasses:

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

class Circle extends Shape {
  constructor(public radius: number) { super(); }
  area(): number { return Math.PI * this.radius ** 2; }
---

Implements Clause

Classes can implement multiple interfaces, guaranteeing they satisfy specific contracts:

interface Serializable { toJSON(): object }
interface Validatable { validate(): boolean }

class User implements Serializable, Validatable { ... }

Generic Classes

Classes can be parameterized with type variables, enabling type-safe collections and services:

class DataStore<T> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  getAll(): T[] {
    return this.items;
  }
---

const store = new DataStore<number>();
store.add(42);
// store.add('hello') // Type error

Generic classes are the foundation for type-safe repositories, cache implementations, and state management stores.

Enums

Enums define named constants. TypeScript supports numeric and string enums.

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

enum Status {
  Active = 'ACTIVE',
  Inactive = 'INACTIVE',
  Pending = 'PENDING',
---

Numeric enums are reverse-mappable (Direction[0] yields 'Up'). String enums do not support reverse mapping but provide more readable serialized values. For modern codebases, const enum eliminates runtime overhead by inlining values during compilation. However, const enum cannot be used with bundlers that emit isolated modules without additional configuration.

Setting Up TypeScript

Initialize a TypeScript project with tsconfig.json:

npm init -y
npm install typescript --save-dev
npx tsc --init

Essential tsconfig.json options:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true
  }
---

The strict: true flag enables all strict type-checking options — strictNullChecks, noImplicitAny, strictFunctionTypes, and others. Start with strict mode enabled to catch more errors at compile time (see the TypeScript Configuration guide for a deeper dive).

Compilation and Execution

npx tsc                    # compile
node dist/index.js         # run
npx ts-node src/index.ts   # compile & run in one step

For watch mode during development, use npx tsc --watch. For Node.js projects, tsx provides a faster alternative to ts-node using esbuild:

npx tsx src/index.ts       # fast TypeScript execution
npx tsx watch src/index.ts # watch mode

Type Acquisition for JavaScript Libraries

When using npm packages that lack built-in types, TypeScript can download type declarations automatically via the types configuration or by installing @types/* packages:

npm install -D @types/react @types/node

The typeRoots compiler option controls where TypeScript looks for type declarations. By default, it checks node_modules/@types. You can add custom type root directories for monorepo shared types.

FAQ

Do I need TypeScript for small projects?
Not strictly, but even small projects benefit from better IDE autocompletion, early error detection, and self-documenting code. TypeScript’s learning curve is minimal for JavaScript developers.

What is the difference between type and interface?
Both define object shapes. Interfaces support declaration merging and are preferred for public APIs; types work with unions, intersections, and mapped types. See the Types vs Interfaces guide for details.

How do I type a callback parameter?
Use a function type: function fetch(url: string, callback: (data: unknown) => void) or a type alias: type Callback = (data: unknown) => void.

Can I mix JavaScript and TypeScript in the same project?
Yes. Set allowJs: true in tsconfig. Files with .js extension are type-checked loosely; rename them to .ts for full coverage.

What does --strict actually enable?
It enables strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, and alwaysStrict. These catch the most common real-world type errors.

Internal Links

References

Section: TypeScript 1460 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top