Modern Naming Conventions in TypeScript: Avoiding the I Prefix and Interface Suffix
Published on
4 min read
Naming conventions are essential for writing clean, readable TypeScript code. An outdated practice, inherited from languages like Java and
C#, is to use an I
prefix or Interface
suffix to differentiate interfaces from classes. In TypeScript, these markers are unnecessary and
can make code harder to read. In this post, we’ll explore a cleaner, more modern approach to naming conventions, even when working with
scenarios that involve multiple implementations of a single type.
The I
Prefix and Interface
Suffix: Outdated and Unnecessary
In Java and C#, developers commonly used an I
prefix (e.g., IProduct
) or an Interface
suffix to distinguish interfaces from concrete
classes. This convention was useful for strongly typed, class-based languages. TypeScript, however, is structurally typed, meaning its types
are defined by structure rather than explicit markers. In TypeScript, these extra markers don’t add clarity and instead make the code
noisier.
Example: Old Naming Convention
Let’s start with an example of the old naming convention using Product
and Order
types:
While this code is perfectly valid, the I
prefix on IProduct
and IOrder
doesn’t contribute to readability. Instead, it introduces
unnecessary noise into the code.
Modern Naming Convention: Descriptive and Concise
A more modern approach in TypeScript is to use simple, descriptive names without prefixes or suffixes. The names Product
and Order
are
straightforward and make the code more readable.
This code is both cleaner and easier to understand. The use of Product
and Order
as type names provides all the information a developer
needs without any extraneous markers.
Why Prefer Type Aliases Over Interfaces?
TypeScript provides both interface
and type
keywords for defining types. While you could mix them, it’s generally better to pick one for
consistency. Many TypeScript engineers prefer using type
for its flexibility. With type
, you can define unions, intersections, and
mapped types, which aren’t possible with interface
.
Key Benefits of Type Aliases
- Union Types: Type aliases allow for unions, such as
type Status = 'pending' | 'completed';
. - Mapped Types: You can create mapped types like
type ReadOnly<T> = { readonly [K in keyof T]: T[K] };
. - Intersections: Type aliases support intersections, making them flexible for combining types, such as
type Item = Product & Order;
.
By using type
consistently, we create a predictable structure in our code. It’s easy to add or modify types without mixing keywords, which
keeps our codebase readable and organized.
Real-World Example: Consistent Naming with Multiple Implementations
To see how this approach applies in a real-world scenario, let’s imagine we have a DataRepository
type with different implementations for
different data sources, such as Redis and PostgreSQL. Instead of using IDataRepository
, we can name the type DataRepository
and name
each implementation based on the data source it represents. Here’s how it might look in practice:
In this example, DataRepository
is the base type that any implementation (e.g., RedisDataRepository
, PostgreDataRepository
) can
follow. This naming approach makes it easy to understand each implementation without extra prefixes or suffixes. Each class name is
descriptive, indicating both the purpose (DataRepository
) and the specific data source (e.g., Redis
, PostgreSQL
), making the code
clearer and easier to maintain.
Summary: Clean Code Through Consistent Naming
Modern TypeScript doesn’t require outdated naming conventions like I
prefixes or Interface
suffixes. By using simple, descriptive names,
you can make your code cleaner and more readable. Using type aliases consistently and choosing meaningful names, such as Product
, Order
,
or DataRepository
, helps maintain a codebase that is both intuitive and adaptable to modern TypeScript practices.
Share on social media platforms.