Have you ever had a conversation like this?

Hopefully you haven’t had a conversation like that in real life, but you might have had one like that with your code!
For example, take a gander at this:
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.
We’ll do this by creating a type alias:
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 Sustenance
each 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:
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.
This version is easier to understand with just a glance:
Indirection
You might also be thinking, “Wait, don’t I have to think more with a type alias? I used to be able to see exactly what types were there, and now they’re hidden behind an alias!”
Sure, we’ve introduced one layer of indirection - there’s some detail that’s masked by the alias. But as programmers, we hide details behind names all the time!
- Instead of writing
9.8
throughout our code, we’d create a constant calledACCELERATION_DUE_TO_GRAVITY
. - Instead of putting
6.28 * radius
, everywhere, we’d put it behind a function calledcircumference()
.
Remember - if we need to see what’s behind the alias, it’s just a Command+Click away in the IDE.
Inheritance
Or maybe you’re thinking, “Why would I need a type alias? I could just use inheritance to create a nickname for complex types, like this:”
You’re correct - in this case, you could just subclass Organization
with its elaborate type argument. You’ve probably seen this done in Java, in fact.
But type aliases also work on types that you can’t or wouldn’t normally inherit from. For example, you can alias:
- Non-
open
classes likeString
, or Java’sOptional<T>
- Singleton
object
instances in Kotlin - Function types, like
(Currency, Coupon?) -> Sustenance
- And even “Function with Receiver” types, like
Currency.(Coupon?) -> Sustenance
We’ll do more of a comparison between a type alias approach and an inheritance approach a little later in this article.
Understanding Type Aliases
We’ve already seen how easy it is to declare a type alias. Now let’s zoom in closer, so we can understand what’s actually happening when we create one!
When dealing with type aliases, we have two types we need to consider:
- The alias.
- The underlying type.

A type that is itself an alias (such as UserId
), or that includes an alias (like List<UserId>
) is said to be abbreviated.
When Kotlin compiles your code, the abbreviated types are expanded into the full, unabbreviated types everywhere that they’re used. Let’s see a more complete example:
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:
Once we get an instance of our Store
, we can make a purchase:
Hang on! Did you notice something?
We accidentally got our arguments mixed up! The userId
is supposed to be the first argument, and the productId
is supposed to be the second!
Yowza! Why didn’t the compiler warn us about this?
If we use our verbatim replacement guideline from above, we can simulate the expansion to see how the compiler views this code:

Whoa! Both of the parameter types are expanded to the same underlying type! That means it’s possible to mix them up, and the compiler will keep on hummin’ right along.
The big takeaway: Type aliases do not create new types. They simply give another name to an existing type.
That, of course, is why we can alias non-open
classes - there’s no subtyping happening.
While you might think this would always be a bad thing, there are actually some situations where it’s helpful!
Let’s compare two different ways of nicknaming a type:
- Using a type alias.
- Using inheritance to create a subtype (as discussed in the section Inheritance above).
The underlying type in both cases will be a String
supplier, which is just a function that takes no argument and returns a String
.
Now, let’s create a couple of functions that accept these suppliers:
And finally, we’re ready to call these functions:
While the aliased version works just fine with a lambda expression, the inherited version doesn’t even compile! Instead, it gives us this error message:
Required: InheritedSupplier / Found: () -> String
In fact, the only way I’ve found to actually call writeInherited()
is to cobble together a verbosity monstrosity like this:
So in this case, the type alias has an edge over the inheritance-based approach.
Of course, there are times when type safety will be the more important characteristic to you, and in those cases, a type alias might not fit your needs.
Examples of Type Aliases
Now that we’ve got a good grasp on type aliases, let’s take a look at some examples! These will give you some ideas about the kinds of types that you might want to alias:
Cool Things You Can Do With Type Aliases

As we’ve seen, once you create an alias, you can use it instead of the underlying type in a variety of scenarios, like:
- When declaring the type of variables, parameters, and return types.
- As type parameter constraints and type arguments.
- When comparing instance types with
is
or casting withas
. - When getting function references.
In addition to these, there are a few uses that warrant some extra detail. Let’s take a look!
Constructors
If the underlying type has a constructor, so will the type alias. You can even invoke the constructor on an alias of a nullable type! For example:
So as you can see, the expansion is not always verbatim, which is helpful here.
If the underlying type has no constructors (such as an interface or a type projection) then you can’t invoke a constructor on the alias either. Naturally.
Companion Object
You can also invoke properties and functions on a companion object
using an alias. This works even if the underlying type has a concrete type argument specified. Check it out:
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:
Interestingly, the private
type alias can appear in a public position, as it does above, where greeting: Message
is public.
Java Interoperability
How do you use a Kotlin type alias from Java code?
You don’t. They aren’t visible from Java.
But if you have Kotlin code that references a type alias, like this…
…then your Java code can continue to interact with it by using the underlying type, like this…
Recursive Aliases
It’s totally fine to make an alias of an alias:
However, you obviously can’t have a recursive type alias definition:
The compiler would error out with this message:
Recursive type alias in expansion: Greeting
Type Projections
If you create a type projection, be careful about your expectations. For example, if we have this code:
… then we might expect this to work:
The problem is that Boxes<out String>
expands to ArrayList<Box<out T>>
, not to ArrayList<out Box<out T>>
.
Import As: The Cousin of Type Alias
There’s a concept that’s very to type aliases, called Import As. It allows you to give a new name to a type, function, or property when you import it into a file. For example:
In this case, we’ve imported the Builder
class from NotificationCompat
, but in the current file, it’ll go by the name NotificationBuilder
.
Have you ever run into a situation where you need to import two classes that have the same name?
If so, then you can imagine how Import As can be a huge help, because it means you don’t have to qualify one of those classes.
For example, check out this Java code, where we translate a database model of a user to a service model of a user.
Since this code deals with two different classes, each called User
, we can’t import them both. So instead, we end up fully qualifying one of them.
With Kotlin’s Import As, we don’t have to fully qualify it - we can just give it another name when we import it!
You might be wondering, “Well, then…. what’s the difference between using a type alias and Import As? After all, you could also disambiguate the User
references with typealias
, 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
andprivate
, whereas imports will be file-scoped. - If you import a class from a package that’s already automatically imported, like
kotlin.*
orkotlin.collections.*
, then you have to reference it by that name. For example, if you were to writeimport kotlin.String as RegularExpression
, then usages of justString
would refer tojava.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.