DEV Community

Cover image for Interface vs. Type in TypeScript: How I Built a Custom ESLint Rule to Convert All Interfaces to Types
Jan Szotkowski
Jan Szotkowski

Posted on • Originally published at Medium

2 2 2 2 2

Interface vs. Type in TypeScript: How I Built a Custom ESLint Rule to Convert All Interfaces to Types

As a front-end developer with over six years of experience, I’ve seen my fair share of TypeScript debates. One that always sparks heated discussions is the classic “Interface vs. Type” dilemma. Early in my career, I didn’t think much of it — both seemed to get the job done. But as projects grew and teams expanded, I realized that small decisions like this can make or break code consistency. That’s why I ended up creating a custom ESLint rule to enforce using type aliases over interface declarations in our TypeScript codebase. In this article, I’ll share why I made that choice, how I built the rule, and how it transformed our workflow. Plus, I’ll throw in a few tips for you to try it out yourself.

The Chaos of Scaling a Team Without Clear Rules

When I started working with TypeScript at my company, our team was small — just a handful of developers. We had a verbal agreement to stick with type aliases instead of interface declarations because we found them more predictable (more on that later). It worked great at first. We were aligned, our code was clean, and life was good. But then our team grew fast. Within a year, we had 10 developers contributing to the same codebase, and that’s when things got messy.

New team members weren’t always aware of our “unwritten rule.” Some preferred interface because it felt more familiar from their Java background. Others mixed both approaches without even noticing. Code reviews turned into endless debates about style rather than substance. It was clear we needed a better way to enforce consistency — something automated, reliable, and scalable. So, I decided to roll up my sleeves and create a custom ESLint plugin: eslint-plugin-interface-to-type. It would automatically enforce the use of type over interface across our entire codebase, saving us from ourselves.

Why I Chose type Over interface

Before diving into the technical details, let’s talk about why I picked type over interface in the first place. At first glance, they might seem interchangeable — they both define shapes for objects, right? But there’s a subtle difference that can bite you in larger projects: Declaration Merging.

In TypeScript, if you declare two interface definitions with the same name, they automatically merge into one. Here’s a quick example:

interface User {
  id: number;
}

interface User {
  name: string;
}

// TypeScript merges them into:
interface User {
  id: number;
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

This behavior can be useful in some cases, like extending third-party library types. But in a collaborative environment, it’s a recipe for confusion. Imagine two developers unknowingly declaring conflicting properties in separate files — it’s a bug waiting to happen. On the other hand, type aliases don’t merge. If you try to declare two type aliases with the same name, TypeScript throws a compilation error, forcing you to resolve the conflict upfront. For me, that predictability is a lifesaver.

Beyond Declaration Merging, type aliases are more flexible. They can represent unions, intersections, and primitives — things interface struggles with. While interface has its place (like when you need to extend classes), I found type to be the safer default for most of our use cases.

Building the ESLint Rule: From Chaos to Consistency

Now that I had a clear reason to enforce type, I needed a tool to make it happen automatically. That’s where my ESLint plugin, eslint-plugin-interface-to-type, comes in. I wanted a rule that would flag any interface declaration, suggest replacing it with a type alias, and even provide an autofix option to streamline the process.

Building a custom ESLint rule was a bit daunting at first — I’d never written one before. But after some research, I got the hang of it. The plugin uses ESLint’s AST (Abstract Syntax Tree) to detect interface declarations and transform them into equivalent type aliases. For example, this:

interface User {
  id: number;
  name: string;
  role: 'admin' | 'user';
}
Enter fullscreen mode Exit fullscreen mode

gets flagged and can be autofixed to:

type User = {
  id: number;
  name: string;
  role: 'admin' | 'user';
};
Enter fullscreen mode Exit fullscreen mode

The hardest part was handling edge cases — like interface declarations that extend other interfaces or have complex generics. I spent hours tweaking the rule to ensure compatibility and avoid false positives. Another challenge was implementing the autofix feature. ESLint’s — fix option requires you to provide precise transformations, so I had to carefully map interface syntax to type syntax without breaking the code.

Once the plugin was ready, setting it up was straightforward. You just install it as a dev dependency:

npm install eslint-plugin-interface-to-type --save-dev
Enter fullscreen mode Exit fullscreen mode

Then add it to your ESLint config:

{
  "plugins": ["interface-to-type"],
  "rules": {
    "interface-to-type/prefer-type-over-interface": "error"
  }
}
Enter fullscreen mode Exit fullscreen mode

Run ESLint with — fix, and it’ll automatically convert your interface declarations to type aliases. Done!

Impact on Our Workflow: Less Fighting, More Building

The impact of this rule on our team was immediate. Before, we’d spend chunks of code reviews arguing over interface vs. type. Now, the rule enforces consistency for us, freeing up mental space for more important discussions — like architecture or performance optimizations.

It also saved us time. New developers no longer needed a lengthy onboarding lecture about our style preferences. The rule just handled it. Code reviews became faster because we weren’t nitpicking over syntax anymore. It’s a small change, but the ripple effects were huge.

Tips for Developers: How to Make This Work for Your Team

If you’re intrigued by this idea, here are a few tips to help you enforce type usage — or any coding standard — in your projects:

  1. Understand Your Needs First: Before enforcing type over interface, make sure it fits your team’s goals. If you rely heavily on Declaration Merging or class implementations, interface might still have a place. For us, type worked better 99% of the time, so it made sense to standardize on it.
  2. Automate with Tools Like ESLint: Don’t rely on verbal agreements — they don’t scale. Tools like ESLint (or Prettier for formatting) can enforce rules consistently across a team. Writing a custom rule might sound intimidating, but it’s a great learning experience and pays off in the long run.
  3. Encourage Buy-In from Your Team: If you’re introducing a new standard, get feedback from your colleagues first. I shared my ESLint rule with the team before rolling it out, and their input helped me catch a few edge cases I’d missed.

Try It Yourself and Let Me Know!

Switching to type aliases and enforcing it with a custom ESLint rule was a game-changer for my team. It made our code more predictable, our reviews more productive, and our onboarding smoother. If you’re dealing with similar issues — or just want to experiment with stricter standards — I’d encourage you to give it a shot.

Start by installing eslint-plugin-interface-to-type and running it on your codebase. See how it feels to have a consistent approach to TypeScript types. Got a different perspective on interface vs. type? Or did the rule uncover some unexpected issues in your project? Drop a comment below — I’d love to hear your thoughts!

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

If you found this post useful, consider leaving a ❤️ or a nice comment!

Got it