Why Without Generics Your Swift Code Will Soon Turn into Spaghetti
\n\nImagine you're writing a pizza delivery app. You need to process orders, customers, couriers — and each time write the same code for sorting, searching, or filtering, only changing the data type? Sounds like any developer's nightmare. This is exactly where Generics in Swift come onto the scene — a powerful tool that allows you to write flexible, reusable code without sacrificing type safety.
\n\nIn this article, we won't just skim through the syntax. We'll break down how typing in Swift and generics work together to make your code not only beautiful but also reliable. You'll learn how to get rid of copy-paste, how to use protocol with associatedtype, and why where clauses are your best friend.
What Are Generics and Why They Change the Game
\n\nGenerics (generic types) are a mechanism that allows you to write functions, structures, classes, and enumerations that work with any data type that satisfies certain conditions. Instead of writing three versions of a function for Int, String, and Double, you write one that works with any type.
In Swift, typing is strict and static. This means the compiler checks types at compile time. Generics do not break this rule — they make it more flexible. The compiler itself infers the specific type when the function is called, but the checks remain.
\n\nFirst Example: A Function Without Generics
\n\n// Bad: three functions for three types\nfunc swapInts(_ a: inout Int, _ b: inout Int) {\n let temp = a\n a = b\n b = temp\n}\n\nfunc swapStrings(_ a: inout String, _ b: inout String) {\n let temp = a\n a = b\n b = temp\n}\n\nfunc swapDoubles(_ a: inout Double, _ b: inout Double) {\n let temp = a\n a = b\n b = temp\n}\n\nAnd now — an elegant solution with generics:
\n\n// Good: one function for all types\nfunc swapValues<T>(_ a: inout T, _ b: inout T) {\n let temp = a\n a = b\n b = temp\n}\n\n// Usage:\nvar x = 10\nvar y = 20\nswapValues(&x, &y) // T = Int\n\nvar name1 = "Alice"\nvar name2 = "Bob"\nswapValues(&name1, &name2) // T = String\n\nSee the magic? <T> is a type placeholder. The compiler itself substitutes the real type when called. This is the foundation of generic programming in Swift.
Generic Types: Creating Your Own Universal Stack
\n\nGenerics in Swift work not only with functions but also with entire types. Let's create a Stack structure — a classic example of a LIFO (Last In, First Out) data structure.
struct Stack<Element> {\n private var items: [Element] = []\n \n mutating func push(_ item: Element) {\n items.append(item)\n }\n \n mutating func pop() -> Element? {\n return items.popLast()\n }\n \n func peek() -> Element? {\n return items.last\n }\n \n var count: Int {\n return items.count\n }\n}\n\n// Usage:\nvar intStack = Stack<Int>()\nintStack.push(1)\nintStack.push(2)\nintStack.push(3)\nprint(intStack.pop() ?? 0) // 3\n\nvar stringStack = Stack<String>()\nstringStack.push("Swift")\nstringStack.push("Generics")\nprint(stringStack.peek() ?? "") // "Generics"\n\nNotice: we specify the Element type in angle brackets when declaring the structure. When creating an instance, we concretize it — Stack<Int> or Stack<String>. The compiler guarantees that you cannot accidentally put a string into an integer stack.
Protocol with associatedtype: Generics for Protocols
\n\nIn Swift, protocols cannot have generic parameters directly. Instead, use