DEV Community

Cover image for Type vs Interface
Prashant Sharma
Prashant Sharma

Posted on

Type vs Interface

In TypeScript development, choosing between type and interface can be confusing. Both are essential for defining object shapes and types, but knowing when to use each can be tricky.
Developers often wonder: When should I use type instead of interface?
How can I extend existing types? What about defining complex types, unions, or tuples? This blog will clarify these concepts, providing guidelines and examples to help you choose the right tool for your TypeScript projects, ensuring cleaner and more efficient code.

  • 1. Basic Type and Interface Declarations.

Type

type User = {
    name: string;
    email: string;
};
// Notation Suggestion:
// Use TUser for type declarations.
Enter fullscreen mode Exit fullscreen mode

Interface

interface User {
    name: string;
    email: string;
};
// Notation Suggestion:
// Use IUser for interface declarations.
Enter fullscreen mode Exit fullscreen mode
  • 2. Extending Types and Interfaces.

To extend a type in TypeScript, you use an intersection (&) to combine it with additional properties. For interfaces, you use the extends keyword to inherit properties from another interface.

type User = {
    name: string;
    email: string;
};

type SuperUser = User & {
    roleAccess: string;
};
Enter fullscreen mode Exit fullscreen mode
interface User {
    name: string;
    email: string;
};

interface SuperUser extends User {
    roleAccess: string;
};
Enter fullscreen mode Exit fullscreen mode

Reasons to Prefer type Over interface.

1. Type Aliases Can Define Non-Object Types.

  • Type aliases are more versatile as they can define primitives, union types, and other non-object types.
type Status = 'Success' | 'Error' | 'Warning';
let status: Status = 'Success';
Enter fullscreen mode Exit fullscreen mode
  • This is invalid with interfaces:
interface Status = 'Success' | 'Error' | 'Warning'; // Invalid
Enter fullscreen mode Exit fullscreen mode
  • Workaround with interfaces, but less intuitive:
interface Response {
    Status: 'Success' | 'Error' | 'Warning';
}
const response: Response = {
    Status: 'Success'
};
Enter fullscreen mode Exit fullscreen mode

2. Union Types

  • You cannot create union types with interfaces.
type Status = string | string[];

const status: Status = ['Success', 'Error'];
Enter fullscreen mode Exit fullscreen mode
  • Invalid with interfaces
interface Status = string | string[]; // Invalid
Enter fullscreen mode Exit fullscreen mode

3. Omitting Fields

  • Easily omit fields in a new type
type User = {
    name: string;
    email: string;
    createdAt: string;
};

type Guest = Omit<User, 'name' | 'email'>;
Enter fullscreen mode Exit fullscreen mode
  • Interface equivalent looks cumbersome
interface User {
    name: string;
    email: string;
    createdAt: string;
}

interface Guest extends Omit<User, 'name' | 'email'> {}; // Works but not pretty
Enter fullscreen mode Exit fullscreen mode

4. Tuples

  • Types handle tuples more elegantly.
type Response = [number, string];

const successResponse: Response = [200, 'Success'];
Enter fullscreen mode Exit fullscreen mode
  • With interfaces
interface Response extends Array<number | string> {
    0: number;
    1: string;
}
Enter fullscreen mode Exit fullscreen mode

5. Using Existing Types

  • You can easily create new types based on existing objects.
const aiServices = {
    id: 1,
    title: 'AI Development',
    description: `Lorem ipsum...`,
    tags: ['#ai', '#artificialIntelligence']
};

type Services = typeof aiServices;

const services: Services[] = [{...}];
Enter fullscreen mode Exit fullscreen mode
  • Using as const for literal types
const successResponse = {
    status: 'Success',
    code: 200,
    data: [{...}]
} as const;
Enter fullscreen mode Exit fullscreen mode

Advantages of Interfaces

1. Declaration Merging

  • Interfaces can be merged, allowing you to add fields to an existing interface.
interface User {
    name: string;
    email: string;
}

interface User {
    createdAt: string;
}

const user: User = {
    name: 'Prashant',
    email: '023prashantsharma@gmail.com',
    createdAt: '01/01/2024'
};
Enter fullscreen mode Exit fullscreen mode

2. Class Implementation

  • Interfaces are ideal for class-based models and allow for more descriptive declarations
interface Student {
    name: string;
    age: number;
    className: string;
}

class StudentImpl implements Student {
    name: string;
    age: number;
    className: string;

    constructor(name: string, age: number, className: string) {
        this.name = name;
        this.age = age;
        this.className = className;
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

  • When to Use type:
    For primitive types, union types, tuples, and when needing more flexibility.
    To create complex type combinations and utility types like Omit.

  • When to Use interface:
    For object type declarations, especially when extending or implementing classes.
    When declaration merging is beneficial.

Official Recommendations

  1. Interfaces for Better Documentation and Readability: Interfaces are often preferred for their readability and self-documenting nature.
  2. Performance Considerations: While interfaces may have slight performance advantages in very large codebases, this is typically negligible for most applications.
  3. Declaration Merging: Interfaces can merge, which can be both a feature and a pitfall. It allows augmenting existing types provided by libraries. By balancing the use of type and interface appropriately, you can leverage the strengths of each to write more effective and maintainable TypeScript code.

Top comments (1)

Collapse
 
enoch91 profile image
Enoch Osarenren

Hello everyone, I hope you're all doing well. I recently launched an open-source project called GraphQLPlaceholder, and I'd love your support. Please check it out and give it a star on GitHub github.com/enochval/graphql-placeh.... Your support would mean a lot to me and help immensely in the project's growth.

Thank you.