Lesson 3 • Beginner
Functions and Closures ⚙️
By the end of this lesson you'll define your own Swift functions with argument labels, default values, variadic and inout parameters — and write closures (Swift's lambdas) to power map and filter. This is how you stop repeating yourself and start building real logic.
What You'll Learn in This Lesson
- Write functions with func, parameters, and a return type (-> Int)
- Use argument labels — external vs internal names, and _ to omit them
- Give parameters default values so callers can skip them
- Accept many arguments with a variadic parameter (Type...)
- Modify a caller's variable in place with an inout parameter
- Write closures and use trailing-closure syntax with map and filter
if, loops, and arrays here. Every block below is real Swift: paste it into the free SwiftFiddle (no install) or run it in Xcode, and check the Output panel against the expected output shown.map and filter ask you for one of these notes ("here's what to do with each item") and run it for you.1️⃣ Functions, Parameters & Return Types
A function is a named block of code you can run on demand. You declare it with the func keyword, list any parameters (the inputs it needs) in parentheses, and after -> state the return type — the kind of value it hands back. If a function has no -> Type, it returns nothing; it just performs an action. Read this worked example and run it before moving on.
// A function packages reusable code under a name.
// Shape: func name(label param: Type) -> ReturnType { ... }
func greet(name: String) -> String {
// "\(name)" is string interpolation — the value is dropped into the text.
return "Hello, \(name)!"
}
// Call it by name. The label "name:" is required at the call site.
print(greet(name: "Alice")) // Hello, Alice!
print(greet(name: "Bob")) // Hello, Bob!
// -> Int means "this function hands back an Int".
func square(of value: Int) -> Int {
return value * value
}
let result = square(of: 6) // result is 9? No — it's 36
print("6 squared is \(result)") // 6 squared is 36
// No "-> Type" means the function returns nothing (it just does a job).
func logLine(_ text: String) {
print("LOG: \(text)")
}
logLine("started") // LOG: startedHello, Alice!
Hello, Bob!
6 squared is 36
LOG: started2️⃣ Argument Labels (External vs Internal Names)
This is Swift's signature feature. Each parameter can have two names: an external argument label that you write when you call the function, and an internal parameter name that you use inside the body. Written func greet(person name: String), callers say greet(person:) while your code uses name — so calls read like sentences. Put _ as the label to omit it entirely at the call site.
// Swift parameters can have TWO names:
// external label -> read at the call site
// internal name -> used inside the function body
// Pattern: func f(externalLabel internalName: Type)
func greet(person name: String, from town: String) -> String {
// Inside, you use the internal names: name, town.
return "\(name) is from \(town)"
}
// Outside, you use the external labels: person, from — reads like a sentence.
print(greet(person: "Alice", from: "London")) // Alice is from London
// Write _ as the external label to REMOVE the label at the call site.
func multiply(_ a: Int, _ b: Int) -> Int {
return a * b
}
print(multiply(4, 5)) // 20 (no labels needed)
// Mix and match: first label omitted, second kept.
func power(_ base: Int, to exponent: Int) -> Int {
var total = 1
for _ in 0..<exponent { total *= base }
return total
}
print(power(2, to: 10)) // 1024Alice is from London
20
1024Your turn. Finish the function below so its call reads naturally. Replace the two ___ blanks using the // 👉 hints, then run it and confirm the output.
// 🎯 YOUR TURN — replace each ___ then run the code.
// Goal: a function whose call site reads like English.
// 1) External label "to", internal name "name".
func sayHello(___ name: String) -> String { // 👉 put the external label before name
return "Hello, \(name)!"
}
// 2) Call it. The label you chose must appear here.
print(sayHello(___: "Sam")) // 👉 use the label, then "Sam"
// ✅ Expected output:
// Hello, Sam!Hello, Sam!___ blanks, run it in SwiftFiddle, and check your output matches.3️⃣ Default Values & Variadic Parameters
Give a parameter a default value with = value and callers can skip it — you write the common case once instead of overloading the function many times. A variadic parameter, written Type..., accepts zero, one, or many arguments; inside the body it arrives as a plain array, so you can loop or reduce over it.
// Default parameter values: callers may skip the argument.
func makeTea(_ kind: String = "Black", sugars: Int = 1) -> String {
return "\(kind) tea with \(sugars) sugar(s)"
}
print(makeTea()) // Black tea with 1 sugar(s)
print(makeTea("Green")) // Green tea with 1 sugar(s)
print(makeTea("Mint", sugars: 0)) // Mint tea with 0 sugar(s)
// Variadic parameter (Type...): accept zero, one, or many values.
// Inside the function, "scores" is just an array [Int].
func average(of scores: Int...) -> Double {
if scores.isEmpty { return 0 }
let total = scores.reduce(0, +) // add them all up
return Double(total) / Double(scores.count)
}
print(average(of: 10, 20, 30)) // 20.0
print(average(of: 7)) // 7.0
print(average()) // 0.0 (no values passed)Black tea with 1 sugar(s)
Green tea with 1 sugar(s)
Mint tea with 0 sugar(s)
20.0
7.0
0.04️⃣ inout Parameters
By default a function receives a copy of each argument and cannot change the caller's variable. An inout parameter changes that: the function works on the original variable. You must pass the argument with an ampersand — double(&score) — so it is obvious at the call site that the variable may be modified. Only a var can be passed to an inout parameter (never a let or a literal).
// Normally parameters are CONSTANTS — a function can't change the
// caller's variable. "inout" lets a function modify it in place.
// Add & to the argument when you call an inout function.
func double(_ number: inout Int) {
number *= 2 // changes the ORIGINAL variable
}
var score = 21
double(&score) // pass &score, not score
print(score) // 42
// Classic example: swap two values without a temp variable at the call site.
func swapValues(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
var x = 1
var y = 9
swapValues(&x, &y)
print("x = \(x), y = \(y)") // x = 9, y = 142
x = 9, y = 15️⃣ Closures (Swift's Lambdas)
A closure is an unnamed function you can store in a variable or hand to another function. Its full shape is { (params) -> ReturnType in body }. Closures are how Swift does functional programming: map transforms every element of an array, and filter keeps only the elements that pass a test. When the closure is a function's last argument you can use trailing-closure syntax — move it outside the parentheses — and use $0, $1 as shorthand for its arguments.
// A closure is an unnamed function you can store and pass around.
// Full shape: { (params) -> ReturnType in body }
let add = { (a: Int, b: Int) -> Int in
return a + b
}
print(add(3, 4)) // 7
// Closures shine as arguments. sorted(by:) takes a closure.
let names = ["Charlie", "Alice", "Bob"]
// Full closure passed as the "by:" argument:
let sortedA = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 < s2
})
print(sortedA) // ["Alice", "Bob", "Charlie"]
// TRAILING CLOSURE: when a closure is the LAST argument, move it
// outside the parentheses. $0 and $1 are the first/second arguments.
let sortedB = names.sorted { $0 < $1 }
print(sortedB) // ["Alice", "Bob", "Charlie"]
// map: transform every element into a new array.
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
print(doubled) // [2, 4, 6, 8, 10]
// filter: keep only the elements that pass a test.
let evens = numbers.filter { $0 % 2 == 0 }
print(evens) // [2, 4]
// Chain them: keep evens, then square each one.
let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }
print(evenSquares) // [4, 16]7
["Alice", "Bob", "Charlie"]
["Alice", "Bob", "Charlie"]
[2, 4, 6, 8, 10]
[2, 4]
[4, 16]Now you try. Fill in the two trailing closures so the array is transformed and filtered as described. The hints show the exact closure to write.
// 🎯 YOUR TURN — fill in the blanks, then run.
let prices = [5, 12, 3, 20, 8]
// 1) Use map with a trailing closure to add 2 to every price.
// $0 stands for the current element.
let plusTwo = prices.map ___ // 👉 { $0 + 2 }
// 2) Use filter to keep only prices greater than 7.
let pricey = prices.filter ___ // 👉 { $0 > 7 }
print(plusTwo) // expected below
print(pricey) // expected below
// ✅ Expected output:
// [7, 14, 5, 22, 10]
// [12, 20, 8][7, 14, 5, 22, 10]
[12, 20, 8]___ with a trailing closure, run it, and confirm both lines match the expected output.Putting It Together: an Order Summary
This small program uses everything from the lesson at once — a default value, a variadic parameter, closures with filter and map, and an inout parameter. Read it line by line; you now understand every part.
// === A tiny order summary — uses every idea from this lesson ===
// Default value (taxRate) + variadic (prices) + return type.
func summarize(_ label: String, taxRate: Double = 0.2, prices: Double...) -> String {
let subtotal = prices.reduce(0, +) // sum with reduce
let tax = subtotal * taxRate
let total = subtotal + tax
return "\(label): subtotal \(subtotal), tax \(tax), total \(total)"
}
print(summarize("Order #1", prices: 10, 20, 30)) // taxRate defaults to 0.2
// Closures + filter + map to build a discounted list.
let cart = [19.99, 4.50, 60.00, 12.00]
let bigItems = cart
.filter { $0 >= 10 } // keep items £10 and over
.map { $0 * 0.9 } // apply a 10% discount
print("discounted big items: \(bigItems)")
// inout to apply a loyalty bonus to a running points total.
func addBonus(_ points: inout Int, amount: Int = 50) {
points += amount
}
var loyaltyPoints = 100
addBonus(&loyaltyPoints)
print("points: \(loyaltyPoints)") // 150Order #1: subtotal 60.0, tax 12.0, total 72.0
discounted big items: [17.991, 54.0, 10.8]
points: 150Common Errors (and the fix)
greet("Alice") but the function is func greet(person name: String). Include the label: greet(person: "Alice"). To allow no label, declare the parameter with _.return value doesn't match the declared -> Type. Make the returned value match the return type (or change the return type). Forgetting return in a multi-line function triggers "Missing return in a function expected to return".let (or a literal) to an inout parameter. Declare it as var and pass it with &: double(&score).let snapshot) if you need each one frozen.Pro Tips
- 💡 Name labels for the reader: aim for calls that read aloud, like
move(from: a, to: b). - 💡 Prefer trailing closures for
map/filter/sorted:arr.map { $0 * 2 }is the idiomatic style. - 💡 Default values beat overloads: one function with
sugars: Int = 1is cleaner than several near-identical functions. - 💡 Reach for
inoutsparingly. Returning a new value is usually clearer than mutating the caller's variable.
📋 Quick Reference — Functions & Closures
| Pattern | Syntax |
|---|---|
| Function + return type | func name(p: Type) -> Return |
| External + internal name | func f(label name: Type) |
| Omit the label | func f(_ x: Int) |
| Default value | func f(x: Int = 0) |
| Variadic | func f(nums: Int...) |
| inout (call with &) | func f(_ n: inout Int) |
| Closure | { (p) -> Return in body } |
| map / filter (trailing) | arr.map { $0 * 2 } |
| reduce | arr.reduce(0, +) |
Frequently Asked Questions
Q: What is the difference between an argument label and a parameter name in Swift?
The argument label is the external name you write at the call site (greet(person:)), and the parameter name is the internal name used inside the function body. Writing 'func greet(person name: String)' means callers say person: but your code uses name. They let calls read like English while keeping clear names internally.
Q: How do I call a function without writing the argument labels?
Put an underscore (_) as the external label, for example 'func multiply(_ a: Int, _ b: Int)'. Then you call it as multiply(4, 5) with no labels. Swift uses the parameter name as the label by default, so _ is how you opt out.
Q: When should I use a closure instead of a named function?
Use a closure when you need a short, one-off piece of logic to pass to another function — like the test for filter or the transform for map. If the logic is reused or long enough to deserve a name, write a named function with func instead.
Q: What does inout do, and why do I need the & symbol?
inout lets a function modify the caller's variable in place instead of receiving a copy. You must pass the argument with & (for example double(&score)) to make it visible that the variable may be changed. You can only pass a var to an inout parameter, never a let constant or a literal.
Q: What is trailing closure syntax?
When a closure is the last argument to a function, you can move it outside the parentheses. So names.sorted(by: { $0 < $1 }) becomes names.sorted { $0 < $1 }. It is the idiomatic Swift style and makes map, filter, and reduce calls much easier to read.
Mini-Challenge: Discount Calculator
No blanks this time — just a brief and an outline to keep you on track. Write the function yourself, run it, and check your output against the expected results in the comments. This is exactly the kind of small, reusable logic real apps are built from.
// 🎯 MINI-CHALLENGE: Discount calculator
// 1. Write func priceAfter(_ price: Double, discount: Double = 0.1) -> Double
// that returns the price with the discount removed.
// (hint: price - price * discount)
// 2. Call it once using the default discount, and once with discount: 0.25.
// 3. Make an array let prices = [100.0, 50.0, 80.0] and use map with a
// trailing closure to apply priceAfter to every price.
// 4. print the original call results and the discounted array.
//
// ✅ Expected (with defaults):
// priceAfter(100) -> 90.0
// priceAfter(100, 0.25) -> 75.0
// prices.map { ... } -> [90.0, 45.0, 72.0]
// your code herepriceAfter(100) -> 90.0
priceAfter(100, 0.25) -> 75.0
prices.map { ... } -> [90.0, 45.0, 72.0]🎉 Lesson Complete!
- ✅
func name(p: Type) -> Returnpackages reusable code; no->means it returns nothing - ✅ Parameters have an external label (call site) and an internal name (body);
_omits the label - ✅ Default values let callers skip arguments; a variadic
Type...accepts many - ✅
inout+&modifies the caller's variable in place - ✅ Closures are unnamed functions; trailing-closure syntax powers
map,filter, andreduce - ✅ Next lesson: Object-Oriented Programming — model your data with classes, structs, and protocols
Sign up for free to track which lessons you've completed and get learning reminders.