Skip to main content
    Courses/TypeScript/Classes and Objects

    Lesson 5 • Intermediate

    Classes and Objects 🏛️

    By the end of this lesson you'll design your own classes in TypeScript — with typed fields, constructors, access modifiers, getters/setters, interfaces, and inheritance — the toolkit behind every real-world TypeScript app.

    What You'll Learn

    • Write a class with typed fields and a constructor
    • Use parameter properties to declare and assign in one line
    • Control access with public, private, protected, and readonly
    • Add getters and setters that run code behind a property
    • Make a class implement an interface (a contract)
    • Extend a parent class with extends and super, plus generics in classes

    1. Classes, Fields & the Constructor

    A class is a blueprint for objects. The constructor is a special method that runs once when you write new ClassName(...); its job is to store the starting values on this — the brand-new object being built. A method is just a function that lives on the object. Read this worked example, run it, then you'll write your own.

    Worked example: a User class

    See how the constructor builds each object and methods read this.

    Try it Yourself »
    JavaScript
    // A class is a BLUEPRINT. You stamp out objects from it with "new".
    console.log("=== Building objects from a class ===");
    
    class User {
      // The constructor runs once, when you write "new User(...)".
      // Its job: store the incoming values on "this" (the new object).
      constructor(name, email, age) {
        this.name = name;     // this = the object being built
        this.email = email;
        this.age = age;
      }
    
      // A method = a function that lives on the object.
      greet() { return "Hi, I'm " + this
    ...

    In TypeScript you add a type to every field and parameter, so the compiler catches mistakes before the code runs. You declare the fields above the constructor with name: string. TypeScript also offers a shortcut called parameter properties: put an access modifier on a constructor parameter and TypeScript declares the field and assigns it for you — no this.name = name boilerplate. (This block is read-only; it's TypeScript-specific syntax.)

    // TypeScript version — note the type annotations the JS above hides.
    
    class User {
      // Declare each field WITH its type, above the constructor:
      name: string;
      email: string;
      age: number;
    
      constructor(name: string, email: string, age: number) {
        this.name = name;     // TS checks the types line up
        this.email = email;
        this.age = age;
      }
    
      greet(): string {       // the ": string" is the RETURN type
        return `Hi, I'm ${this.name} (${this.age})`;
      }
    }
    
    // ---- Parameter properties: the same class, written shorthand ----
    // A modifier (public/private/readonly) on a constructor PARAMETER
    // declares the field AND assigns it for you. No "this.x = x" needed.
    
    class UserShort {
      constructor(
        public name: string,
        public email: string,
        public age: number
      ) {}                    // empty body — TS wired it all up
    
      greet(): string {
        return `Hi, I'm ${this.name} (${this.age})`;
      }
    }

    2. Access Modifiers & readonly

    Modifiers decide who can touch a field. public (the default) is readable everywhere; private is reachable only inside the same class; protected adds subclasses to that circle; and readonly lets a field be set once (in the constructor) and then locks it. These checks are TypeScript-only — they protect you while you type, then vanish at runtime — so this is a read-only block.

    // Access modifiers control WHO can touch a field. (TypeScript-only.)
    
    class BankAccount {
      public  owner: string;        // ✅ readable everywhere (public is the default)
      private balance: number;      // 🔒 only inside THIS class
      protected pin: string;        // 🔒 this class AND subclasses, not outside
      readonly id: string;          // set once (in the constructor), then frozen
    
      constructor(owner: string, balance: number, id: string) {
        this.owner = owner;
        this.balance = balance;
        this.id = id;               // ✅ readonly fields may be set in the constructor
      }
    
      deposit(amount: number): void {
        this.balance += amount;     // ✅ private is reachable from inside the class
      }
    }
    
    const acc = new BankAccount("Alice", 1000, "ACC-001");
    console.log(acc.owner);         // ✅ "Alice"  (public)
    // console.log(acc.balance);    // ❌ TS2341: 'balance' is private
    // acc.id = "ACC-002";          // ❌ TS2540: 'id' is read-only

    3. Getters & Setters

    A getter looks like a plain property when you read it (temp.fahrenheit, no parentheses) but actually runs code — great for values you want to compute on the fly. A setter runs when you assign (temp.celsius = 30), which is the perfect place to validate. By convention the backing field is named with a leading underscore, like _celsius.

    Worked example: computed properties

    Read a getter with no parentheses; a setter validates on assignment.

    Try it Yourself »
    JavaScript
    // Getters and setters look like a plain property but run CODE.
    console.log("=== Getters & setters ===");
    
    class Temperature {
      constructor(celsius) { this._celsius = celsius; } // _name = "internal"
    
      // "get" runs when you READ the property (no parentheses!)
      get celsius() { return this._celsius; }
      get fahrenheit() { return this._celsius * 9 / 5 + 32; }
    
      // "set" runs when you ASSIGN to the property — perfect for validation
      set celsius(value) {
        if (value < -273.15) throw new Error
    ...

    Your turn. The Book class below is almost finished — fill in the five blanks marked ___ using the // 👉 hints, then run it and check your output.

    🎯 Your turn: store fields in a constructor

    Fill in the ___ blanks, then check against the expected output.

    Try it Yourself »
    JavaScript
    // 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
    console.log("=== Build a Book ===");
    
    class Book {
      constructor(title, author, pages) {
        // 1) Store each parameter on "this":
        this.title  = ___;     // 👉 title
        this.author = ___;     // 👉 author
        this.pages  = ___;     // 👉 pages
      }
    
      // 2) Return a one-line summary using the fields:
      describe() {
        return ___ + " by " + ___ + " (" + this.pages + " pages)";
        // 👉 use this.title and this.author
      }
    }
    
    let
    ...

    4. Inheritance: extends & super

    extends says "this class is a kind of that one" and inherits its fields and methods. Inside a subclass constructor you must call super(...) first — that runs the parent's constructor before you touch this. A subclass can override a method by redefining it; each object then uses its own version. Read the worked example, then complete the exercise below it.

    Worked example: shapes inherit from Shape

    super() runs the parent constructor; each subclass overrides area().

    Try it Yourself »
    JavaScript
    // extends = "is a kind of".  super = "the parent's version".
    console.log("=== Inheritance ===");
    
    class Shape {
      constructor(name, color) { this.name = name; this.color = color; }
      describe() { return this.color + " " + this.name; }
      area() { return 0; }                 // a default, overridden below
    }
    
    class Circle extends Shape {
      constructor(radius, color) {
        super("circle", color);            // run Shape's constructor FIRST
        this.radius = radius;              // ...then add Circl
    ...

    Now you try. Make Dog inherit from Animal, call super in its constructor, and override speak(). Fill in the four blanks:

    🎯 Your turn: extend a class

    Wire up extends, super, and an overridden method.

    Try it Yourself »
    JavaScript
    // 🎯 YOUR TURN — finish the subclass using extends and super.
    console.log("=== Animals ===");
    
    class Animal {
      constructor(name) { this.name = name; }
      speak() { return this.name + " makes a sound"; }
    }
    
    // 1) Make Dog inherit from Animal:
    class Dog extends ___ {        // 👉 Animal
      constructor(name, breed) {
        // 2) Call the PARENT constructor first (it sets this.name):
        ___(name);                 // 👉 super
        this.breed = breed;
      }
    
      // 3) Override speak() to return name + " bar
    ...

    5. Implementing an Interface

    An interface is a contract: a list of methods and properties a class promises to provide. Writing class Product implements Comparable tells TypeScript "check that Product has everything Comparable requires." If a method is missing you get error TS2420 at compile time, long before users hit it. A class can implement several interfaces at once. Here's the TypeScript contract, then a runnable class that fulfils it:

    interface Serializable {
      toJSON(): string;
    }
    interface Comparable<T> {
      compareTo(other: T): number;
    }
    
    // One class, two contracts — TS checks BOTH are satisfied:
    class Product implements Serializable, Comparable<Product> {
      constructor(public name: string, public price: number) {}
      toJSON(): string { return JSON.stringify({ name: this.name, price: this.price }); }
      compareTo(other: Product): number { return this.price - other.price; }
    }

    Worked example: a class that fulfils a contract

    Because every Product has compareTo, the array sorts cleanly.

    Try it Yourself »
    JavaScript
    // In TS, "class Product implements Comparable" is a PROMISE that the
    // class supplies every method the interface lists. JS has no interfaces,
    // so here we just show the class that the contract describes.
    console.log("=== implements a contract ===");
    
    // TS interface (for reference):
    //   interface Comparable {
    //     compareTo(other: Product): number;
    //   }
    
    class Product {
      constructor(name, price) { this.name = name; this.price = price; }
    
      // The method the interface required:
      compare
    ...

    🔎 Deep Dive: Generics in Classes

    Just like functions, a class can take a type parameter written in angle brackets, conventionally <T>. That lets one class work with any type while staying fully type-safe — a Box<number> only ever holds numbers, a Box<string> only strings, with no casting and no any.

    class Box<T> {
      private value: T;
      constructor(value: T) { this.value = value; }
      get(): T { return this.value; }        // returns the SAME type you put in
    }
    
    const n = new Box<number>(42);
    const s = new Box<string>("hi");
    n.get();    // typed as number
    s.get();    // typed as string
    // new Box<number>("oops");  // ❌ TS2345: string is not assignable to number

    You'll meet this constantly in real code: Array<T>, Map<K, V>, and Promise<T> are all generic classes from the standard library.

    Common Errors (and the fix)

    • "TS2341: Property 'balance' is private and only accessible within class 'BankAccount'" — you read a private field from outside. Expose it through a public method or a getter (e.g. get balance()) instead of reaching in directly.
    • "TS2420: Class 'Product' incorrectly implements interface 'Comparable'" — the class is missing a method (or its signature is wrong) that the interface requires. Add the missing member with the exact name and types the interface lists.
    • "TS2564: Property 'name' has no initializer and is not definitely assigned in the constructor" (using a property before assignment) — every declared field must get a value in the constructor. Assign this.name = ..., give it a default, or mark it optional with name?: string.
    • "'super' must be called before accessing 'this'" — in a subclass constructor, call super(...) before any use of this.
    • "TS2540: Cannot assign to 'id' because it is a read-only property" — a readonly field can only be set in the constructor. Remove the later assignment, or drop readonly if the value really must change.

    Pro Tips

    • 💡 Reach for parameter properties (constructor(public name: string)) to delete repetitive this.x = x lines.
    • 💡 Make fields private by default and open them up only when needed — it keeps your objects' internals safe to change later.
    • 💡 Need true runtime privacy? TypeScript's private is compile-time only; use a native #field for privacy JavaScript enforces at runtime too.
    • 💡 Prefer interfaces + composition over deep inheritance. Long extends chains get rigid fast; small contracts stay flexible and testable.

    📋 Quick Reference

    FeatureSyntaxMeaning
    Typed fieldname: string;A field that must hold text
    Param propertyconstructor(public x: number)Declares & assigns in one line
    Privateprivate balance: numberOnly inside this class
    Protectedprotected pin: stringThis class + subclasses
    Readonlyreadonly id: stringSet once, then locked
    Getterget area() { ... }Runs code on property read
    Implementsclass X implements Y, ZPromise to fulfil a contract
    Extendsclass Dog extends AnimalInherit from a parent
    Generic classclass Box<T> { ... }Works with any type, safely

    Frequently Asked Questions

    Q: What's the difference between a class and an interface?

    A class is real code you can new to build objects; it has fields and method bodies. An interface is only a type-checking contract — a list of what must exist, with no implementation. A class can implements an interface to prove it satisfies that contract.

    Q: When should I use parameter properties vs declaring fields normally?

    Use parameter properties when the constructor just stores its arguments — it removes the repetitive this.x = x lines. Declare fields the long way when a field isn't a direct constructor argument, or when it needs extra setup.

    Q: Is TypeScript's private truly hidden at runtime?

    No. private is enforced only by the compiler; the compiled JavaScript still exposes the field. For privacy that survives at runtime, use a native private field with a # prefix, like #balance.

    Q: Why must I call super() before using this in a subclass?

    Because the parent's constructor is what actually sets up the object's inherited fields. Until super() runs, this isn't fully built, so JavaScript forbids touching it.

    Mini-Challenge: Build a Stack

    No blanks this time — just a brief and an outline. Write the Stack class yourself (a "last in, first out" list), then uncomment the test lines and check your output against the expected lines. This is the kind of small, reusable class real apps are full of.

    🎯 Mini-Challenge: a Stack class

    Build push, pop, peek, and a size getter from the outline.

    Try it Yourself »
    JavaScript
    // 🎯 MINI-CHALLENGE: a Stack
    console.log("=== Stack ===");
    
    // 1. Make a class "Stack" whose constructor sets  this.items = [];
    // 2. push(item): add item to the end of this.items
    // 3. pop(): remove and RETURN the last item
    // 4. peek(): return the last item WITHOUT removing it
    // 5. get size(): a GETTER returning this.items.length
    //
    // Then run the test lines at the bottom — they should print the
    // expected output below.
    
    // your code here
    
    
    // ---- test (uncomment once your class exists) -
    ...

    🎉 Lesson Complete

    • ✅ A class is a blueprint; the constructor builds each object on this
    • Parameter properties declare and assign a field in one line
    • public / private / protected / readonly control access
    • Getters & setters run code behind a property — perfect for computed values and validation
    • implements proves a class fulfils an interface's contract
    • extends + super give inheritance; <T> makes a class generic
    • Next lesson: Advanced Types — unions, intersections, and mapped types

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service

    Install LearnCodingFast

    Learn faster with the app on your home screen.