When you see a chunk of code with so many types smushed together, it’s easy to get lost in the details. In fact, it’s intimidating just looking at those functions!

Thankfully, Kotlin gives us an easy way to simplify this complex type into something way more readable - type aliases.

In this article:

We’re going to learn all about type aliases and how they work.

Then, we’re going to look at some ways you might want to use them.

And then we’ll look at a few gotchas to watch out for.

And finally, we’ll take a look at a similar concept, Import As, and see how it compares.

Wheels up - let’s go!

Introducing Type Aliases

Once we’ve coined a term for a concept, we don’t have to describe that concept every time we talk about it - we just use the term instead! So let’s do the same thing for our code - let’s take this complex type and give it a name.

Now, instead of describing the concept of a restaurant everywhere - that is, instead of writing out Organization<(Currency, Coupon?) -> Sustenance> each time - we can just write the term Restaurant, like this:

Wow! So much easier on the eyes, and there’s a lot less thinking you have to do when you look at it!

We’ve also avoided a lot of duplication of types throughout the RestaurantPatron interface - instead of writing out Organization, Currency, Coupon?, and Sustenanceeach time, we’ve got just one type - Restaurant.

This also means that if we needed to change that complex type in any way - for example, if we wanted to specialize it to this: Organization<(Currency, Coupon?) -> Meal> - then we can just change it in one spot instead of three:

typealias Restaurant = Organization<(Currency, Coupon?) -> Meal>

Easy!

You might be thinking…

Readability

You might be saying to yourself, “I don’t see how this helps readability… Why would I need the type to be Restaurant in the example above, when the parameter name already clearly says restaurant? Can’t we use concrete parameter names and abstract types?”

Yes, the name of the parameter does explain the the type in more concrete terms, as it should. But the aliased version of our RestaurantPatron interface above is still more readable and less intimidating.

Also, there are cases where you either don’t have names, or they’re farther removed from the type. For example:

In this code, it’s still possible to tell that the locator is returning a list of restaurants, but the only clue we have about that is the name of the interface. The essence of the locator function type gets lost in the verbosity.

When the compiler processes this, all of the references to UserId get expanded into UniqueIdentifier.

In other words, as a general rule, if you were to search your code for all usages of the alias (UserId), and replace them verbatim with the underlying type (UniqueIdentifier), you’d roughly be doing the same thing as the compiler does during expansion.

You might have noticed I used the words “for the most part” and “roughly”. That’s because, although this is a good starting point for our understanding of type aliases, there are a handful of cases where Kotlin is extra helpful by not doing a completely verbatim replacement. We’ll explore those soon! For now, we’ll just keep in mind that this verbatim replacement guideline is generally helpful.

By the way, if you’re using IntelliJ IDEA, you’ll be glad to know that you get some nifty support for type aliases. For example, you can see both the alias name and the underlying type in code completion:

And in quick documentation:

Type Aliases and Type Safety

Now that we’ve got the basics of type aliases down, let’s explore another example. This one makes use of multiple aliases:

Again, we see that Kotlin doesn’t always do verbatim replacement, especially in cases where it’s helpful to do something else.

Gotchas

There are a few other things to keep in mind as you use type aliases.

Top-Level Only

Type aliases are top-level only. In other words, they can’t be nested inside a class, object, interface, or other code block. If you try to do this, you’ll get this error message from the compiler:

Nested and local type aliases are not supported.

However, you can restrict their visibility with the usual visibility modifiers like internal and private. So if you want a type alias to be accessible only from within one class, you’d need to put the type alias and the class in the same file, and mark the alias as private, like this:

That’s correct. In fact, as it so happens, other than the metadata, these two versions of UserService compile down to the same bytecode!

So why would you choose one over the other? What are the differences? Here’s a list of different things you might want to alias or import, and whether they’re supported by each:

Target

Type Alias

Import As

Interfaces and Classes

Yes

Yes

Nullable Types

Yes

No

Generics with Type Params

Yes

No

Generics with Type Arguments

Yes

No

Function Types

Yes

No

Enums

Yes

Yes

Enum Members

No

Yes

object

Yes

Yes

object Functions

No

Yes

object Properties

No

Yes

As you can see, some targets are only supported by one or the other.

Here are a few other things to keep in mind:

Type aliases can have visibility modifiers like internal and private, whereas imports will be file-scoped.

If you import a class from a package that’s already automatically imported, like kotlin.* or kotlin.collections.*, then you have to reference it by that name. For example, if you were to write import kotlin.String as RegularExpression, then usages of just String would refer to java.lang.String. Yikes!

By the way, if you’re an Android developer and you’re using Kotlin Android Extensions in your project, Import As is a fantastic way to map those snake_cased XML IDs to camelCased references that look like the rest of the variables in your activity:

This can make your transition from findViewById() (or Butter Knife) to Kotlin Android Extensions very easy!

Wrap-up

Type aliases can be a great way to take complex, verbose, and abstract types and give them simple, concise, and domain-specific names. They’re easy to use, and the tooling support gives you insight into the underlying types. Used in the right place, they can make your code easier to read and understand.