Gleam Type Aliases
A type alias gives a new name to an existing type. Aliases do not create new types — they create readable shortcuts for types that would otherwise be verbose or repeated across many function signatures.
Defining a Type Alias
type UserId = Int
type Email = String
type Score = Float
After these declarations, UserId and Int are completely interchangeable. The compiler treats them as identical — an alias is purely a readability tool.
Why Use Aliases
Without alias — hard to read:
──────────────────────────────────────────────────
pub fn send_invite(id: Int, address: String, points: Float) -> Bool
With aliases — intent is clear:
──────────────────────────────────────────────────
type UserId = Int
type Email = String
type Score = Float
pub fn send_invite(id: UserId, address: Email, points: Score) -> Bool
The alias version tells you what each argument means at a glance. The plain-type version requires you to read the documentation or count parameters to understand the purpose of each Int and String.
Aliases for Complex Types
Aliases shine when the underlying type is long or nested:
type UserMap = Map(String, User)
type ScoreList = List(#(String, Int))
type MaybeUser = Option(User)
type UserResult = Result(User, String)
Without alias:
pub fn lookup(m: Map(String, User), key: String) -> Option(User)
With alias:
pub fn lookup(m: UserMap, key: String) -> MaybeUser
Aliases Are Transparent to the Compiler
The compiler sees through aliases. If a function expects Int and you pass a UserId, that is fine — they are the same type.
type UserId = Int
pub fn double(n: Int) -> Int { n * 2 }
let id: UserId = 7
let result = double(id) // Works — UserId is Int
This transparency is a strength for convenience but also a limitation: the compiler cannot distinguish a UserId from a plain Int. If you need the compiler to enforce separation, use a custom single-field type (a wrapper type) instead.
Wrapper Types vs Aliases
Alias — transparent, no enforcement:
──────────────────────────────────────────────────
type UserId = Int
type ProductId = Int
// The compiler CANNOT tell these apart
// You can pass a ProductId where a UserId is expected
Wrapper type — opaque, enforced:
──────────────────────────────────────────────────
type UserId { UserId(value: Int) }
type ProductId { ProductId(value: Int) }
// The compiler WILL catch mixups
// UserId and ProductId are distinct types
Generic Type Aliases
Aliases can include type parameters:
type Pair(a, b) = #(a, b)
type MaybeList(a) = Option(List(a))
let coords: Pair(Int, Int) = #(10, 20)
let numbers: MaybeList(Int) = Some([1, 2, 3])
Common Standard Library Aliases
The standard library uses several aliases you encounter often:
Common Aliases in the Ecosystem
──────────────────────────────────────────────────
Alias │ Expands To
─────────────────┼──────────────────────────────
BitArray │ Built-in binary data type
UtfCodepoint │ Built-in Unicode code point
Practical Example — E-Commerce Domain
import gleam/map
type ProductId = Int
type CustomerId = Int
type Quantity = Int
type Price = Float
type Inventory = Map(ProductId, Quantity)
pub fn check_stock(inv: Inventory, product: ProductId) -> Quantity {
case map.get(inv, product) {
Ok(qty) -> qty
Error(_) -> 0
}
}
pub fn can_fulfill(inv: Inventory, product: ProductId, needed: Quantity) -> Bool {
check_stock(inv, product) >= needed
}
Every function signature reads like plain English. check_stock takes an Inventory and a ProductId — you immediately understand what each parameter represents without looking up documentation.
When to Use Aliases
Decision Guide
──────────────────────────────────────────────────
Use alias when:
✓ A type appears in many function signatures
✓ The raw type name obscures intent (Int, String)
✓ A complex generic type repeats everywhere
✓ You want to document purpose through naming
Use wrapper type when:
✓ You need the compiler to prevent mixing types
✓ The concept is distinct enough to deserve a type
Key Points
Type Alias Essentials
──────────────────────────────────────────────────
1. Declare with: type AliasName = ExistingType
2. Alias and original type are interchangeable
3. Improves readability — no runtime cost
4. Does NOT enforce type separation
5. Supports type parameters: type Pair(a) = #(a, a)
6. Use wrapper types when enforcement matters
Type aliases are a zero-cost documentation tool. They cost nothing at runtime and add significant clarity at read time. Use them freely to give meaningful names to raw types and to tame complex generic signatures.
