How To Use TypeScript Interfaces For Better Code Organization And Readability
Tags: webdev, typescript, programming, beginners
Published: 2023-02-18 15:10:44

[TypeScript](https://www.typescriptlang.org/) is an add-on for JavaScript. That is, it includes more features and improvements such as syntax for types. TypeScript runs with a compile-time type checker hence you can avoid or debug errors faster when you add types to your code. According to its [official website](https://www.typescriptlang.org/), it is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.

In TypeScript, there are several ways to represent objects, one of those ways includes interfaces. An interface controls the property names an object can have, this means that when an object implements an interface, the object conforms to the rules specified by the interface.

Interfaces can help achieve code organization and readability which is crucial for maintaining, collaborating, reusing, and optimizing your codebase. In this article, we'll discuss:

1. [Importance Of Interfaces](#the-importance-of-interfaces)
2. [Syntax And Basic Use Cases Of Interfaces](#syntax-and-basic-use-cases-of-interfaces)
   - [Syntax](#syntax)
   - [Basic Use Cases](#basic-use-cases)
3. [Conclusion](#conclusion)

##Importance Of Interfaces

Interfaces provide a range of benefits, from improving code consistency and readability to facilitating reuse and catching errors early in the development process. In this section, we'll discuss a few importance of using interfaces in your TypeScript code.

1. **Enforce consistency**: Interfaces let you define a common set of properties and methods that objects must implement, ensuring that the structure and behavior of objects of a given type are the same. 

2. **Facilitate documentation**: Interfaces serve as a place to document the expected properties and methods of an object. 

3. **Improve Readability**: By providing the expected structure of an object, including its properties, methods, and types, interfaces make code easier to read especially when working with complex codebases or collaborating with other developers.

4. **Catch errors early**: Enforcing strict type-checking at compile time, interfaces help catch errors before they make it to production. This can save time, effort, and cost.

5.  **Code Reusability**: Since interfaces allow you to define custom types that can be used in place of built-in types like `number` and `string`, it can ensure that code is more reusable thereby reducing duplication and also make the code modular - this means you can export and import it.

##Syntax And Basic Use Cases Of Interfaces

In this section, we'll get started with interfaces in TypeScript. It's important to understand their syntax and basic use cases - defining and implementing interfaces, and how to use them to enforce type safety in your code. 


> **NOTE**: To follow along with the code snippets provided, you can use the official [TypeScript playground](https://www.typescriptlang.org/play).


###Syntax

Interfaces are defined using the `interface` keyword followed by its name, then the body of the interface in curly braces. Here's an example of an interface.

```ts
interface User {
firstName: string;
lastName: string;
}
``` 
We defined an interface and named it `User`, it has properties `firstName` and `lastName`, both of which are of type `string`. 

We can now use the `User` interface as a natural data type.

```ts
interface User {
firstName: string;
lastName: string;
}

//define object that conforms to the specification of interface User

const user: User = {
  firstName: 'John',
  lastName: 'Doe'
}
```

Objects using the `User` interface as their type must have the same properties(the keys in the object), as specified in the `User` interface. If a property is optional, it can be omitted. If one of the properties is missing or if its value isn't the same data type specified in the interface, then the compiler will throw an error. Hence we cannot add extra fields that are not defined in the interface to the object. 

Let's try to add a new property that was not initially defined in the interface and see what happens:

```ts
interface User {
firstName: string;
lastName: string;
}

const user: User = {
  firstName: 'John',
  lastName: 'Doe',
  id:'123' //new property
}
```
The above code will cause the TypeScript compiler to throw an error `2322`, since the `id` property was not initially declared in the interface:

```ts
Type '{ firstName: string; lastName: string; id: string; }' is not assignable to type 'User'.
Object literal may only specify known properties, and 'id' does not exist in type 'User'.(2322)
```
###Basic Use Cases

Now that we already know how interfaces are defined, let's discuss a few use cases.

####Use Case: Optional Properties

To make a property optional as discussed earlier, you append `?` to its name as seen below:

```ts

interface User {
firstName: string;
lastName: string;
id?: string; //id can now be omitted and the compiler won't throw any errors
}

const user: User = {
  firstName: 'John',
  lastName: 'Doe',  
}

```
####Use Case: Read-only Properties

In cases where we want to make a property `read-only` in an interface, we include the `readonly` keyword before the name of the property:

```ts
interface User {
firstName: string;
lastName: string;
id?: string;
readonly dbid: number; 
}

const user: User = {
    firstName: 'John',
    lastName: 'Doe',
    dbid: 22
}
```

After `dbid`'s first assignment in the `user` object, it cannot be reassigned again. Using the `readonly` keyword with a property means the property cannot be modified after its first assignment. You can think of its behavior as the same as the `const` keyword in JavaScript. Hence, the code below will throw an error `2540`:

```ts
interface User {
firstName: string;
lastName: string;
id?: string;
readonly dbid: number; 
}

const user: User = {
    firstName: 'John',
    lastName: 'Doe',
    dbid: 22
}

user.dbid = 4;
```   
Error message: 
```ts
Cannot assign to 'dbid' because it is a read-only property.(2540)
```

####Use Case: Extending Other Types

When creating new interfaces, we can extend our interfaces so they include properties from other interfaces or types. This way you can extract interfaces that contain the same properties and use them as components for other interfaces.

From the `User` interface in previous examples, let's define a new interface `Admin` that also contains the `firstName` and `lastName` properties but also contains a new property `role`. We can simply extend the `Admin` interface from the `User` interface. 

See code example below:

```ts

interface User {
firstName: string;
lastName: string;
id?: string;
readonly dbid: number; 
}

interface Admin extends User {
   role: 'admin' | 'user' | 'developer' 
}

const admin: Admin = {
    firstName: 'John',
    lastName: 'Doe',
    dbid: 22,
    role: 'admin'
}

```
The `Admin` interface now inherits the properties of the interface `User` and also includes its own property `role`. It's just as if we did:

```ts

interface Admin {
firstName: string;
lastName: string;
id?: string;
readonly dbid: number; 
role: 'admin' | 'user' | 'developer' 
}

```
Extending an interface is not just limited to other interfaces. They can extend from any object type like [normal types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases), and also [classes](https://www.digitalocean.com/community/tutorials/how-to-use-classes-in-typescript).

####Use Case: Function Types

We can use interfaces to describe function types. That is, we can define the expected parameter type and expected return type of a function in an interface.

See code example below:

```ts
interface AddFunction {
  (a: number, b: number): number;
}

const add: AddFunc = (a, b) => {
  return a + b;
}

```
We defined an interface `AddFunction` that describes how the parameters and return value of any function that utilizes the interface should look like. You see that after we declared the interface, we didn't need to specify the parameter and return type in the `add` function anymore. 

> **Note**: The names of the parameters in our function do not need to match the ones defined in our function type.

####Use Cases: Index Signatures

Index signatures are a way of defining the shape of an object with dynamic property names or when the properties of an object are not known yet.

In the code below, we'll see how we can define an index signature for an object whose properties we don't know yet:

```ts
interface Score {
  [key: string]: number;
}

const score: Score = {
  score1: 1,
  score2: 2,
  score3: 3
}

```
In the above example, we defined an interface `Score` that has an index signature. The `key` is a string, and the `value` is a number. This simply means that any object(like the `score` object above), that utilizes the interface must have its `keys` as type string and `values` as type number.

##Conclusion

While we've discussed some common use cases of interfaces, you should not that there are many more use cases of interfaces in TypeScript. Whether you're working on a large codebase with multiple developers or you just want to improve the quality of your own code, interfaces can be a valuable tool to have in your knowledge base.

If you're interested in learning more about interfaces in TypeScript, you can explore the documentation and start experimenting with different use cases. 

In conclusion, interfaces are a powerful feature of TypeScript that can provide a range of benefits, from improving code consistency and readability to facilitating reuse and catching errors earlier in the development process. By enforcing consistent types across a codebase, interfaces can help ensure that code is easier to read and understand, more modular and reusable, and less error-prone. 

If you have any questions, feel free to ask them in the comment section.