Functions are reusable blocks of code that perform specific tasks. They’re fundamental to organising your code, avoiding repetition, and making your programs easier to understand and maintain. Think of a function as a mini-program that takes some input, does something useful with it, and optionally gives you back a result.

In Swift, functions are first-class citizens—you can pass them around, store them in variables, and use them just like any other data type.

What is a Function?

A function is a named piece of code that you can call (or “invoke”) from elsewhere in your program. Functions help you:

  • Avoid repetition: Write code once, use it many times
  • Organise logic: Break complex problems into smaller, manageable pieces
  • Make testing easier: Test individual functions in isolation
  • Improve readability: Give meaningful names to operations

Basic Function Syntax

Here’s the simplest possible function in Swift:

func sayHello() {
    print("Hello, world!")
}

// Call the function
sayHello() // Prints: Hello, world!

Let’s break down the syntax:

  • func keyword declares a function
  • sayHello is the function name
  • () indicates this function takes no parameters
  • {} contains the function body—the code that runs when called

Typical Use Cases:

  • Setting up initial app state
  • Performing one-time configuration tasks
  • Triggering simple actions like showing alerts

Functions with Parameters

Most functions need input to do their work. Parameters let you pass data into functions:

func greet(name: String) {
    print("Hello, \(name)!")
}

greet(name: "Alice")    // Prints: Hello, Alice!
greet(name: "Bob")      // Prints: Hello, Bob!

You can have multiple parameters:

func calculateArea(width: Double, height: Double) {
    let area = width * height
    print("Area: \(area) square units")
}

calculateArea(width: 10.0, height: 5.0) // Prints: Area: 50.0 square units

Parameter Labels

Swift functions use parameter labels to make function calls more readable:

func sendMessage(to recipient: String, with content: String) {
    print("Sending '\(content)' to \(recipient)")
}

sendMessage(to: "john@example.com", with: "Meeting at 3pm")

The function call reads almost like English: “send message to john@example.com with Meeting at 3pm”.

Typical Use Cases:

  • Processing user input
  • Configuring UI elements with specific values
  • Performing calculations with different inputs

Return Values

Functions can return results using the -> arrow syntax:

func add(a: Int, b: Int) -> Int {
    return a + b
}

let sum = add(a: 5, b: 3)
print(sum) // Prints: 8

For simple functions, you can omit the return keyword:

func multiply(a: Int, b: Int) -> Int {
    a * b  // Implicit return
}

let product = multiply(a: 4, b: 6) // 24

Multiple Return Values

Swift functions can return multiple values using tuples:

func getNameAndAge() -> (String, Int) {
    return ("Alice", 25)
}

let (name, age) = getNameAndAge()
print("\(name) is \(age) years old") // Prints: Alice is 25 years old

You can also name the tuple elements for clarity:

func analyzeText(_ text: String) -> (wordCount: Int, charCount: Int) {
    let words = text.split(separator: " ").count
    let characters = text.count
    return (words, characters)
}

let analysis = analyzeText("Hello Swift programming")
print("Words: \(analysis.wordCount), Characters: \(analysis.charCount)")

Typical Use Cases:

  • Mathematical calculations and transformations
  • Data processing and formatting
  • Validation functions that return success/failure states

Default Parameter Values

You can provide default values for parameters, making them optional when calling the function:

func createUser(name: String, age: Int = 18, isActive: Bool = true) {
    print("Created user: \(name), age \(age), active: \(isActive)")
}

createUser(name: "Alice")                    // Uses defaults
createUser(name: "Bob", age: 25)             // Override age only
createUser(name: "Charlie", age: 30, isActive: false) // Override both

Typical Use Cases:

  • API functions with sensible defaults
  • Configuration functions where most parameters rarely change
  • UI setup functions with standard styling options

Variadic Parameters

Sometimes you don’t know how many values will be passed to a function. Variadic parameters accept zero or more values:

func calculateAverage(_ numbers: Double...) -> Double {
    guard !numbers.isEmpty else { return 0 }
    
    let sum = numbers.reduce(0, +)
    return sum / Double(numbers.count)
}

print(calculateAverage(1.0, 2.0, 3.0, 4.0, 5.0)) // Prints: 3.0
print(calculateAverage())                         // Prints: 0.0

The ... syntax creates an array from the passed values.

Typical Use Cases:

  • Mathematical functions that work with any number of values
  • Logging functions that accept multiple messages
  • Functions that combine or process collections of similar items

Inout Parameters

By default, function parameters are constants—you can’t modify them. If you need to modify a parameter and have that change reflected outside the function, use inout:

func doubleValue(_ number: inout Int) {
    number *= 2
}

var myNumber = 5
doubleValue(&myNumber)  // Note the & symbol
print(myNumber) // Prints: 10

The & symbol when calling the function indicates you’re passing a reference to the variable, not just its value.

Typical Use Cases:

  • Functions that modify data structures in place
  • Performance-critical code where copying large values is expensive
  • Utility functions that need to update multiple related values

Function Types

In Swift, every function has a type based on its parameters and return value. You can assign functions to variables:

func addNumbers(a: Int, b: Int) -> Int {
    a + b
}

func multiplyNumbers(a: Int, b: Int) -> Int {
    a * b
}

// Function type: (Int, Int) -> Int
let mathOperation: (Int, Int) -> Int = addNumbers

print(mathOperation(3, 4)) // Prints: 7

// Change which function we're using
mathOperation = multiplyNumbers
print(mathOperation(3, 4)) // Prints: 12

Functions as Parameters

You can pass functions as parameters to other functions:

func processNumbers(_ a: Int, _ b: Int, using operation: (Int, Int) -> Int) -> Int {
    return operation(a, b)
}

let result1 = processNumbers(5, 3, using: addNumbers)     // 8
let result2 = processNumbers(5, 3, using: multiplyNumbers) // 15

Typical Use Cases:

  • Creating flexible, configurable algorithms
  • Callback functions for handling events
  • Higher-order functions for data processing

Nested Functions

You can define functions inside other functions. Nested functions have access to variables from their enclosing function:

func createCounter(startingFrom start: Int) -> () -> Int {
    var current = start
    
    func increment() -> Int {
        current += 1
        return current
    }
    
    return increment
}

let counter = createCounter(startingFrom: 10)
print(counter()) // Prints: 11
print(counter()) // Prints: 12
print(counter()) // Prints: 13

Typical Use Cases:

  • Creating specialised helper functions
  • Building closures with captured state
  • Organizing complex logic into smaller, focused pieces

Best Practices

1. Use Descriptive Names

Choose function names that clearly describe what the function does:

// Good
func calculateMonthlyPayment(principal: Double, rate: Double, years: Int) -> Double

// Less clear
func calc(p: Double, r: Double, y: Int) -> Double

2. Keep Functions Focused

Each function should do one thing well:

// Good: Single responsibility
func validateEmail(_ email: String) -> Bool {
    // Email validation logic
    return email.contains("@") && email.contains(".")
}

func sendWelcomeEmail(to email: String) {
    // Email sending logic
    print("Sending welcome email to \(email)")
}

// Less ideal: Multiple responsibilities
func validateAndSendEmail(_ email: String) {
    // Validation AND sending in one function
}

3. Use Parameter Labels for Clarity

Make your function calls read naturally:

// Good: Clear intent
func move(from startPosition: CGPoint, to endPosition: CGPoint)

// Less clear
func move(_ start: CGPoint, _ end: CGPoint)

Wrapping Up

Functions are essential building blocks in Swift programming. They help you write clean, reusable, and maintainable code. Start with simple functions and gradually explore more advanced features like function types and nested functions as your projects grow.

The key is to practice writing functions that are:

  • Clear: Easy to understand what they do
  • Focused: Do one thing well
  • Reusable: Can be called from multiple places

Happy coding!