Skip to main content
    Courses/Swift/Functions and Closures

    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

    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.

    Worked example: func, a return type (-> Int), and a void function
    // 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: started
    Output
    Hello, Alice!
    Hello, Bob!
    6 squared is 36
    LOG: started
    This is real code — run it for free atSwiftFiddleor in your own editor.

    2️⃣ 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.

    Worked example: external labels, internal names, and _ to omit
    // 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))      // 1024
    Output
    Alice is from London
    20
    1024
    This is real code — run it for free atSwiftFiddleor in your own editor.

    Your 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: add an argument label
    // 🎯 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!
    Output
    Hello, Sam!
    Fill in the ___ 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.

    Worked example: default values and a variadic parameter
    // 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)
    Output
    Black tea with 1 sugar(s)
    Green tea with 1 sugar(s)
    Mint tea with 0 sugar(s)
    20.0
    7.0
    0.0
    This is real code — run it for free atSwiftFiddleor in your own editor.

    4️⃣ 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).

    Worked example: modify a variable in place with inout
    // 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 = 1
    Output
    42
    x = 9, y = 1
    This is real code — run it for free atSwiftFiddleor in your own editor.

    5️⃣ 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.

    Worked example: closure syntax, trailing closures, map & filter
    // 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]
    Output
    7
    ["Alice", "Bob", "Charlie"]
    ["Alice", "Bob", "Charlie"]
    [2, 4, 6, 8, 10]
    [2, 4]
    [4, 16]
    This is real code — run it for free atSwiftFiddleor in your own editor.

    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: map and filter with trailing closures
    // 🎯 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]
    Output
    [7, 14, 5, 22, 10]
    [12, 20, 8]
    Replace each ___ 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.

    Worked example: defaults + variadic + closures + inout
    // === 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)")   // 150
    Output
    Order #1: subtotal 60.0, tax 12.0, total 72.0
    discounted big items: [17.991, 54.0, 10.8]
    points: 150
    This is real code — run it for free atSwiftFiddleor in your own editor.

    Common Errors (and the fix)

    "Missing argument label 'person:' in call" — you wrote 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 _.
    "Cannot convert return expression of type 'String' to return type 'Int'" — your 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".
    "Cannot pass immutable value as inout argument" — you passed a let (or a literal) to an inout parameter. Declare it as var and pass it with &: double(&score).
    Closure capture surprise: a closure captures the variables it uses, so it sees their value when it runs, not when it was written. A closure built inside a loop that captures the loop variable can all share the final value — capture a copy (e.g. a 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 = 1 is cleaner than several near-identical functions.
    • 💡 Reach for inout sparingly. Returning a new value is usually clearer than mutating the caller's variable.

    📋 Quick Reference — Functions & Closures

    PatternSyntax
    Function + return typefunc name(p: Type) -> Return
    External + internal namefunc f(label name: Type)
    Omit the labelfunc f(_ x: Int)
    Default valuefunc f(x: Int = 0)
    Variadicfunc f(nums: Int...)
    inout (call with &)func f(_ n: inout Int)
    Closure{ (p) -> Return in body }
    map / filter (trailing)arr.map { $0 * 2 }
    reducearr.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: build priceAfter and map it over an array
    // 🎯 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 here
    Output
    priceAfter(100)        -> 90.0
    priceAfter(100, 0.25)  -> 75.0
    prices.map { ... }     -> [90.0, 45.0, 72.0]
    Write the code yourself in SwiftFiddle, then compare your output to the expected results shown.

    🎉 Lesson Complete!

    • func name(p: Type) -> Return packages 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, and reduce
    • 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.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service