Understanding typealias in Swift
A typealias gives an existing type a second name. Nothing new is created and nothing is wrapped: the alias and the original are the same type, and the compiler treats them as interchangeable everywhere.
typealias Age = Int
func celebrate(person: String, age: Age) {
print("\(person) is now \(age)")
}
celebrate(person: "Alice", age: 30) // Program output:
// Alice is now 30 That signature reads better than one with a bare Int in it, and the payoff grows with the type. The real work this keyword does is on closures, tuples, dictionaries, and generic types, where the spelled-out version fills a whole line.
Syntax
typealias NewName = ExistingType The new name is an identifier that follows the same rules as any type name, so start it with a capital letter. The existing type can be anything the compiler can name: a standard library type, one of yours, a tuple, a closure signature, or a generic specialisation.
An alias can be declared at file scope or nested inside a struct, class, enum, or protocol. It also takes access-control keywords, so private typealias keeps a helper name out of the rest of the module.
Naming a Closure You Pass Around
Closure types are where the readability gain is largest, because the spelled-out form has to be repeated at every use site:
import Foundation
typealias CompletionHandler = (Result<Data, Error>) -> Void
func fetch(from path: String, then completion: @escaping CompletionHandler) {
completion(.success(Data()))
}
fetch(from: "/articles") { result in
switch result {
case .success(let data): print("Loaded \(data.count) bytes")
case .failure(let error): print("Failed: \(error)")
}
} // Program output:
// Loaded 0 bytes One name now covers the callback for every request in the file. Change the payload later and there is a single line to edit rather than one per function.
Note that @escaping stays on the parameter, not in the alias. Attributes like that belong to the position the closure is used in, so an alias cannot carry them.
Naming Tuples and Dictionaries
Tuples have no name of their own, which makes them the type most improved by getting one. Naming the elements at the same time is worth the extra characters:
typealias Point2D = (x: Double, y: Double)
typealias WordCounts = [String: Int]
let origin: Point2D = (x: 0, y: 0)
var counts: WordCounts = ["swift": 2]
counts["set", default: 0] += 1
print(origin.x, counts.sorted { $0.key < $1.key }) // Program output:
// 0.0 [(key: "set", value: 1), (key: "swift", value: 2)] The labelled form gives you origin.x instead of origin.0, which survives a later reordering of the elements. Dictionary aliases pay off in the same way once the key or value type is long enough to wrap.
Once a tuple grows past two or three elements, or starts being returned from public functions, a struct is usually the better call. It gets a real name, methods, and the ability to conform to protocols. The alias is a convenience, not a substitute for modelling.
Generic Aliases
An alias can take its own type parameters and pass them through to the underlying type:
typealias StringKeyed<Value> = [String: Value]
typealias Callback<T> = (T) -> Void
let ages: StringKeyed<Int> = ["Alice": 30, "Bob": 25]
let log: Callback<String> = { print("log: \($0)") }
log("\(ages.count) people") // Program output:
// log: 2 people You can also pin a generic type down to one concrete case, which is the more common use. typealias IntStack = Stack<Int> gives a specific specialisation a short name without introducing a parameter.
typealias Inside a Protocol
Protocols use associatedtype to leave a type open, and conforming types fill it in. A typealias is how a conformer states that choice explicitly:
protocol Renderer {
associatedtype Output
func render(_ text: String) -> Output
}
struct HTMLRenderer: Renderer {
typealias Output = String
func render(_ text: String) -> String { "<p>\(text)</p>" }
}
print(HTMLRenderer().render("Hi")) // Program output:
// <p>Hi</p> The alias here is usually optional, because Swift infers Output from the return type of render. Write it out when the inference is not obvious to a reader, or when the compiler cannot work it out on its own.
What a typealias Deliberately Does Not Do
It does not give you a distinct type. This compiles, and for some purposes that is a problem:
typealias Metres = Double
typealias Seconds = Double
let distance: Metres = 100
let time: Seconds = 9.58
let nonsense = distance + time
print(nonsense) // Program output:
// 109.58 Adding metres to seconds is meaningless, and the compiler had no way to object because both names mean Double. When you want the compiler to enforce the distinction, wrap the value in a struct instead:
struct Metres {
let value: Double
}
// Metres(value: 100) + 9.58 no longer compiles That is the trade. An alias costs nothing and buys readability; a wrapper type costs a little ceremony and buys type safety. Choose by whether mixing the values up would actually be a bug.
Pitfalls and Debugging
Circular Reference
Two aliases that define each other have no underlying type to resolve to:
typealias A = B
typealias B = A // Compiler error:
// error: type alias 'A' references itself The fix is to anchor one end on a real type. Aliases can chain, so typealias A = Int followed by typealias B = A is fine and resolves in one step.
Cannot Find Type in Scope
An alias declared inside a type or marked private is not visible from outside that scope, and the error names the alias rather than the scope:
struct Parser {
private typealias Row = [String: String]
}
// let row: Parser.Row = [:]
// error: 'Row' is inaccessible due to 'private' protection level Order does not matter at file scope, so an alias used above its declaration still compiles. Access level and nesting are what actually break lookup, so check those before moving lines around.
An Alias That Hides What Broke
Type errors are reported against the underlying type, which is why messages mention a type you did not write:
typealias Scores = [Int]
let scores: Scores = ["a"] // Compiler error:
// error: cannot convert value of type 'String' to expected element type 'Int' Nothing is wrong with the alias. Read past the name to what it resolves to, which Xcode shows on hover and the compiler often prints as Scores (aka Array<Int>).
When is a typealias worth it?
Use one when the underlying type is long, repeated, and stable: callback signatures, dictionary shapes that appear in several signatures, and generic specialisations you name often. Those are the cases where the alias removes noise without removing information.
Skip it when the alias only renames something short, when the new name is vaguer than the original, or when what you actually wanted was a distinct type. An alias that a reader has to look up has cost more than it saved.
Frequently Asked Questions
Does a typealias create a new type?
No. It introduces a second name for a type that already exists, and the compiler treats the two as interchangeable everywhere. If you want a name the compiler will enforce as distinct, define a struct that wraps the underlying value.
Can a typealias be private?
Yes. It takes the same access levels as any other declaration, so private, fileprivate, internal, and public all apply. A public alias to an internal type will not compile, because the name would expose something callers cannot see.
Can you alias your own class or struct?
Yes, and there is no restriction on which kind of type you alias. Writing typealias Human = Person is legal, and Human then means exactly the Person class, including its initialisers and inheritance.
What is the difference between typealias and associatedtype?
A typealias names a type that is already known. An associatedtype is a placeholder inside a protocol that each conforming type fills in, often by declaring a typealias, though the compiler usually infers it from the method signatures.
Does a typealias cost anything at runtime?
No. It is resolved during compilation, so the generated code is identical to writing the underlying type by hand. There is no wrapper, no indirection, and nothing to unwrap.
Should you alias a simple type like Int?
Only when the alias adds meaning a reader needs, such as Age or Milliseconds in a signature full of numbers. Aliasing Int to Number adds a name to learn without adding information, which is the usual way this feature gets misused.
Self-Check
-
What does this print?
typealias Age = Int let myAge: Age = 25 print(myAge)- A. A compilation error
- B. Age
- C. 25
- D. 25 years old
-
Which is a valid declaration?
- A.
typealias Person = struct { name: String } - B.
typealias FullName = (String, String) - C.
alias Name = String - D.
typealias = Int
- A.
-
What is the type of
colouraftertypealias RGB = (Int, Int, Int)andvar colour: RGB = (255, 0, 0)?- A. A custom RGB struct
- B. A tuple of three integers
- C. An array of three integers
- D. A compilation error
-
How would you name a closure taking two
Intvalues and returning aBool?- A.
typealias Compare = (Int, Int) -> Bool - B.
typealias Compare = (Int, Int) - C.
typealias Compare = Int -> Bool - D.
typealias Compare = () -> Bool
- A.
-
Given
typealias StringMap = [String: String], what is the type oflet result: StringMap = ["name": "Alice"]?- A.
[Int: Int] - B.
[String: Any] - C.
[String: String] - D.
[String]
- A.
- True or false: a
typealiascreates a type the compiler will keep separate from the original. -
What is a
typealiasfor?- A. Creating new data types
- B. Naming existing types for readability
- C. Defining constants
- D. Replacing class inheritance
-
What does this print?
typealias Point2D = (x: Double, y: Double) let point: Point2D = (x: 3.0, y: 4.0) print(point.x)- A. 3.0
- B. 4.0
- C. A compilation error
- D. (3.0, 4.0)
Answers
- C.
AgeisInt, so the value prints as the integer25. - B. The right side must be an existing type, and a tuple type qualifies. Option A tries to declare a struct, C uses a keyword that does not exist, and D has no name.
- B. The alias renames the tuple type; it does not build a struct or an array.
- A. A closure type needs both the parameter list and the return type. Option C is missing the parentheses that Swift requires around parameters.
- C. The alias resolves to
[String: String]exactly, with no widening toAny. - False. The alias and the original are the same type, so mixing them up is not something the compiler can catch. A wrapper struct is what gives you separation.
- B. It adds a name, not a type, and it has nothing to do with constants or inheritance.
- A. The labelled tuple gives
point.x, which holds3.0.
Related
- Structs vs Classes in Swift - when a wrapper type beats an alias.
- allSatisfy in Swift - a natural home for a named predicate alias.
- Swift - the language hub and its learning path.
Sources
-
[1]
Declarations: Type Alias Declaration(docs.swift.org)
-
[2]
Generics: Associated Types(docs.swift.org)
Read Next
Structs copy and classes share, and that one difference drives inheritance, memory management, mutability, and identity. A criteria-based way to pick the right one for a Swift type.
The allSatisfy(_:) method answers one question about a whole collection: does every element pass this test? Syntax, the empty-collection result that surprises people, and when filter is the method you actually wanted.
A practical Swift guide to optionals, value types, closures, async/await, SwiftUI state, and reviewing AI-assisted Apple platform code.