TypeScript Defining and Using Interfaces Step by step Implementation and Top 10 Questions and Answers
 .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    Last Update: April 01, 2025      18 mins read      Difficulty-Level: beginner

TypeScript: Defining and Using Interfaces

TypeScript interfaces provide a way to define the structure of objects or classes. They act as contracts within your code or between different modules, ensuring that implementations conform to specified patterns. By defining interfaces, TypeScript allows developers to articulate their intentions clearly, making it easier to catch errors during development and improving overall software design.

What Are Interfaces?

In TypeScript, an interface is a blueprint that describes the properties and methods an object should have. It can also define complex data structures like nested objects. Interfaces are purely a compile-time construct; they do not exist in JavaScript after compilation, which keeps them lightweight and efficient. Here is a simple example:

interface Vehicle {
    make: string;
    model: string;
    year: number;
}

This interface indicates that any object implementing Vehicle must have make, model, and year properties.

How to Define Interfaces

  1. Basic Interfaces: Interfaces are created with the interface keyword. Basic interfaces can define properties and methods.

    interface Person {
        firstName: string;
        lastName: string;
        getFullName(): string;
    }
    
  2. Optional Properties: You can denote optional properties by appending a ? at the end of property names.

    interface Square {
        sideLength?: number;
        color?: string;
    }
    
  3. Readonly Properties: Properties can be prefixed with readonly to indicate that they cannot be changed after their initial assignment.

    interface Point {
        readonly x: number;
        readonly y: number;
    }
    

    The best way to ensure that the values of these properties are not modified is by allocating new object literals directly to them. Attempting to change a readonly property will result in an error during compilation.

  4. Function Types: Interfaces can define callable types that represent functions.

    interface SearchFunc {
        (source: string, subString: string): boolean;
    }
    
    let mySearch: SearchFunc;
    mySearch = function(source: string, subString: string) {
        let result = source.search(subString);
        return result > -1;
    }
    
  5. Indexable Types: Indexable types allow you to define the type returned when indexing your types with either string or number.

    interface Employee {
        readonly [index: string]: string;
    }
    
    let employee: Employee;
    employee = ["Alice", "Bob", "Charlie"];
    employee["alice"] = "Engineer";
    
  6. Extending Interfaces: Interfaces can extend each other, inheriting their members.

    interface Shape {
        color: string;
    }
    
    interface Square extends Shape {
        sideLength: number;
    }
    
    let square: Square = {color: "blue", sideLength: 10};
    
  7. Hybrid Types: A type can be both a function and an object, combining different types into one.

    interface Counter {
        (start: number): string;
        interval: number;
        reset(): void;
    }
    
    function getCounter(): Counter {
        let counter = <Counter>function (start: number) { };
        counter.interval = 123;
        counter.reset = function () { };
        return counter;
    }
    
    let c = getCounter();
    c(10);
    c.reset();
    c.interval = 5.0;
    
  8. Class Types: Interfaces can be implemented by classes.

    interface ClockInterface {
        currentTime: Date;
        setTime(d: Date): void;
    }
    
    class Clock implements ClockInterface {
        currentTime: Date;
        setTime(d: Date) {
            this.currentTime = d;
        }
        constructor(h: number, m: number) {}
    }
    
    
  9. Interfaces Extending Classes: An interface can extend a class, picking up all properties and methods from the class, including private and protected members.

    This allows you to create an interface that can only be implemented by classes that derive from a given base class.

    class Control {
        private state: any;
    }
    
    interface SelectableControl extends Control {
        select(): void;
    }
    
  10. Differences Between Interface and Type Aliases: Both interfaces and type aliases can describe the shape of an object, but they do so with some differences. Type aliases can describe primitives, unions, intersections, tuples, and more; whereas interfaces are primarily focused on describing object shapes.

    interface User {
        name: string;
        id: number;
    }
    
    type AnotherUser = {
        name: string;
        id: number;
    };
    
    // Interfaces are open while types are closed
    interface Window {
        title: string;
    }
    
    interface Window {
        area: number;
    }
    
    const win: Window = {
        title: "Sample Window",
        area: 100,
    };
    

Using Interfaces

Once an interface is defined, you can use it to type-check objects, function parameters, and class members to ensure they adhere to the required structure.

// Typing Object Literals
let car: Vehicle = {
   make: "Toyota",
   model: "Corolla",
   year: 2020,
};

// Interfaces in Function Parameters
function printPerson(p: Person): void {
    console.log(`${p.firstName} ${p.lastName}`);
}

printPerson({firstName: "John", lastName: "Doe"});

// Interfaces and Classes

class MotorVehicle implements Vehicle {
    make: string;
    model: string;
    year: number;

    constructor(make: string, model: string, year: number) {
        this.make = make;
        this.model = model;
        this.year = year;
    }
}

// Interfaces and Arrays
let fibonacciNumbers: number[] = [0, 1, 1, 2, 3, 5, 8];
let stringArray: string[] = ["foo", "bar", "baz"];

// Interfaces and Generics
function identity<T>(arg: T): T {
    return arg;
}

interface GenericIdentityFn<T> {
    (arg: T): T;
}

let myIdentity: GenericIdentityFn<number> = identity;

Important Information

  • Compile Time Only: As mentioned earlier, interfaces are erased during the compilation process. This makes them ideal for design-time checks but does not add runtime cost.

  • Consistency: When using interfaces, ensure consistency across your project. This makes the code easier to read and maintain.

  • Open/Closed: Unlike type aliases, interfaces are open-ended, meaning you can extend them without creating a new definition. This can be useful in larger codebases where multiple parts of the codebase need to contribute to the same type structure.

  • Union Types: Interfaces cannot represent union types (| operator). If you need to model a type that can be one of many different shapes, use type aliases or a combination of classes and inheritance.

  • Intersection Types: Interfaces can be extended through intersection types, allowing them to include properties from multiple sources.

  • Utility Types: Interfaces can work well with utility types such as Partial<T> or Required<T>, which can make certain properties optional or mandatory.

    interface Book {
         title: string;
         author: string;
         publishedDate: Date;
    }
    
    const bookInfo: Partial<Book> = {
         title: "1984",
    };
    
    const fullyDefinedBook: Required<Book> = {
         title: "1984",
         author: "George Orwell",
         publishedDate: new Date("1949-06-08"), 
    };
    

Practical Example

Here's a practical example combining some of these concepts:

// Define a basic interface
interface Animal {
    name: string;
}

// Define an interface that extends another interface
interface Dog extends Animal {
    breed: string;
    bark(): void;
}

// Implement the interface with a class
class GoldenRetriever implements Dog {
    name: string;
    breed: string;

    constructor(name: string) {
        this.name = name;
        this.breed = "Golden Retriever";
    }

    bark() {
        console.log("Woof woof!");
    }
}

// Use the class implementing the interface
const myDog = new GoldenRetriever("Max");
console.log(myDog.name);  // Output: Max
myDog.bark();  // Output: Woof woof!

// Implement interface in a function parameter
function adoptAnimal(animal: Animal) {
    console.log(`You adopted an animal named: ${animal.name}`);
}
adoptAnimal(myDog);

// Use readonly property
interface ReadonlyPoint {
    readonly x: number;
    readonly y: number;
}

const origin: ReadonlyPoint = {x: 10, y: 20};
// origin.x = 30;  // Uncommenting this line results in a compile-time error

Conclusion

Using interfaces in TypeScript is a powerful way to ensure that objects conform to a defined structure and contract. They promote clear, maintainable, and error-resistant code by enabling advanced typing, method abstraction, and structural subtyping. While understanding and using interfaces correctly requires a bit of getting used to, they are undoubtedly a valuable tool in a professional TypeScript developer’s toolkit.




Examples, Set Route and Run the Application Then Data Flow Step by Step for Beginners

Topic: TypeScript Defining and Using Interfaces

TypeScript is a statically typed superset of JavaScript that brings type safety, interfaces, and other advanced features. One of the most powerful features of TypeScript is the ability to define interfaces, which specify the structure of objects in your code. This helps maintain consistency and reduces bugs. Let's walk through an example of defining and using interfaces in TypeScript. We'll also outline the steps to set up a basic project, define routes, and see the data flow.

Step-by-Step Guide

Step 1: Set Up a TypeScript Project

First, make sure you have Node.js and npm (Node Package Manager) installed on your machine. You can download them from here.

  1. Create a new project folder:

    mkdir typescript-interface-example
    cd typescript-interface-example
    
  2. Initialize a new npm project:

    npm init -y
    
  3. Install TypeScript as a development dependency:

    npm install typescript --save-dev
    
  4. Initialize TypeScript in your project:

    npx tsc --init
    

    This command creates a tsconfig.json file that specifies the TypeScript compiler options.

  5. Install express and @types/express for creating a Node.js server:

    npm install express
    npm install @types/express --save-dev
    

Step 2: Define a TypeScript Interface

Open the tsconfig.json file and make sure your rootDir and outDir are set correctly. The rootDir is where TypeScript looks for your source files, and outDir is where the compiled JavaScript files will go.

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}

Create a src directory and inside it, create a file named app.ts:

  1. Create the src directory:

    mkdir src
    
  2. Define an interface for a User object:

    In src/app.ts, add the following code to define an interface and use it in a basic Express server:

    import express, { Request, Response } from 'express';
    
    // Define an interface for the User object
    interface User {
        id: number;
        name: string;
        email: string;
    }
    
    // Create an array of User objects
    const users: User[] = [
        { id: 1, name: 'Alice', email: 'alice@example.com' },
        { id: 2, name: 'Bob', email: 'bob@example.com' }
    ];
    
    // Initialize express
    const app = express();
    const PORT = 3000;
    
    // Set a route to fetch all users
    app.get('/users', (req: Request, res: Response) => {
        res.json(users);
    });
    
    // Start the server
    app.listen(PORT, () => {
        console.log(`Server is running on http://localhost:${PORT}`);
    });
    

Step 3: Compile TypeScript and Run the Application

  1. Compile the TypeScript code:

    npx tsc
    

    This command compiles the TypeScript files in the src directory into JavaScript and places them in the dist directory.

  2. Run the compiled JavaScript application:

    node dist/app.js
    

    You should see the message Server is running on http://localhost:3000 in your console.

Step 4: Test the Application

Open a web browser and navigate to http://localhost:3000/users. You should see the JSON response with the list of users:

[
    { "id": 1, "name": "Alice", "email": "alice@example.com" },
    { "id": 2, "name": "Bob", "email": "bob@example.com" }
]

Data Flow Explained

  1. Defining the Interface:

    • We defined an interface User with three properties: id, name, and email.
    • This interface acts as a blueprint for User objects.
  2. Creating the Server:

    • We initialized an Express server using express().
    • We defined a route /users that handles GET requests.
  3. Handling Requests:

    • When a GET request is received at the /users route, the server responds with the JSON array of User objects.
    • The responses are automatically formatted as JSON due to res.json(users).
  4. Running the Application:

    • The TypeScript files are compiled into JavaScript.
    • The JavaScript files are executed by Node.js to start the server.
    • The server listens for incoming requests and processes them based on the defined routes.

By following these steps, you've created a simple TypeScript application that uses interfaces to define data structures and an Express server to handle HTTP requests. This example demonstrates how TypeScript can help you write more robust and error-free code.




Top 10 Questions and Answers on TypeScript: Defining and Using Interfaces

1. What is an Interface in TypeScript?

Answer: In TypeScript, an interface is a way to define the shape of objects, classes, or functions. It specifies the properties and methods that an object should have, ensuring consistency and enabling static type checking. Interfaces are a powerful tool for defining contracts within your code as well as contracts with code outside your project.

2. How do you define an Interface in TypeScript?

Answer: You can define an interface using the interface keyword followed by the interface name and curly braces {}. Inside the curly braces, you declare the properties and methods that should be available. Here is an example:

interface User {
    name: string;
    age: number;
    greet(): void; // Method signature
}

This interface User specifies that any object conforming to this interface must have a name property of type string, an age property of type number, and a method named greet without a return value.

3. Can Interfaces contain optional properties?

Answer: Yes, interfaces can include optional properties, which are marked with a ?. Optional properties are useful when an object might have additional properties that aren't always present:

interface UserConfig {
    name: string;
    age?: number; // Optional property
}

In this example, the age property is optional.

4. How do you extend an Interface in TypeScript?

Answer: In TypeScript, you can extend an existing interface using the extends keyword. This allows you to create a new interface that inherits properties and methods from one or more base interfaces:

interface Animal {
    name: string;
}

interface Dog extends Animal {
    breed: string;
    bark(): void;
}

Here, Dog interface extends the Animal interface and adds a breed property and a bark method.

5. What are Index Signatures in interfaces?

Answer: Index signatures allow an interface to describe types for properties that were not known at design time, but that are valid based on some sort of pattern. They can be defined using square brackets []:

interface StringDictionary {
    [key: string]: string; // All properties must be strings
}

In this case, the StringDictionary interface describes an object where all keys are strings and all values are also strings. This is particularly useful for objects acting as maps or dictionaries.

6. How can interfaces describe call signatures (functions)?

Answer: Interfaces can define functions by specifying a call signature using parentheses () and a return type. Here's an example:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

The SearchFunc interface represents any function that takes two string arguments and returns a boolean.

7. Can interfaces have constructor signatures?

Answer: No, interfaces cannot directly specify a constructor signature. However, you can achieve similar functionality by defining an interface for the class itself and then another interface for constructors. Here's how:

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
  tick(): void;
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
  return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
    console.log("beep beep");
  }
}

let digital = createClock(DigitalClock, 12, 17);

Here, ClockConstructor serves as the constructor interface, used to check if a class correctly implements the interface and has the right constructor parameters.

8. How do you use an Interface to annotate a variable?

Answer: You can use an interface to annotate a variable to ensure it adheres to the interface structure:

interface Car {
    make: string;
    model: string;
    year: number;
}

const myCar: Car = {
    make: 'Toyota',
    model: 'Corolla',
    year: 2021
};

In this example, myCar is annotated with the Car interface, ensuring that it has a make, model, and year property.

9. How can Interfaces be implemented by Classes?

Answer: Classes in TypeScript can implement interfaces by using the implements keyword. This enforces that the class implements all properties and methods defined in the interface:

interface Animal {
    name: string;
    move(distanceMeters: number): void;
}

class Bird implements Animal {
    name = 'Parrot';

    move(distanceMeters: number) {
        console.log(`${this.name} flew ${distanceMeters}m.`);
    }
}

Here, the Bird class implements the Animal interface, providing a name property and a move method with the same signature as defined in the interface.

10. Can Interfaces be mixed into other types (like unions or intersections)?

Answer: Yes, interfaces can be mixed with other types to create more complex types using unions (|) and intersections (&). This allows you to compose types flexibly:

interface Pet {
    name: string;
}

interface Dog extends Pet {
    breed: string;
}

type DogOrCat = Dog | { breed: string; species: 'cat' };

const myPet: DogOrCat = {
    name: 'Whiskers',
    breed: 'Persian',
    species: 'cat'
};

In this example, DogOrCat is a union type that can be a Dog or an object with a breed and species property. This demonstrates the flexibility of combining interfaces with other types.

Understanding these concepts will help you effectively utilize interfaces in TypeScript to create robust and maintainable code structures.