Skip to content

Instantly share code, notes, and snippets.

@cgcardona
Created February 6, 2026 14:46
Show Gist options
  • Select an option

  • Save cgcardona/c7a5c46d23ef4fc0ed93a5fb4d4c779f to your computer and use it in GitHub Desktop.

Select an option

Save cgcardona/c7a5c46d23ef4fc0ed93a5fb4d4c779f to your computer and use it in GitHub Desktop.

Swift Basics Tutorial

A comprehensive guide to the fundamental concepts of Swift programming.


Table of Contents

  1. Variables
  2. Data Types
  3. Functions
  4. Optionals
  5. Control Flow
  6. Structs

Variables

Swift provides two ways to declare variables and constants:

Constants with let

Constants are declared with let and cannot be changed after their initial value is set.

let maximumLoginAttempts = 10
let welcomeMessage = "Hello, World!"

// This would cause an error:
// maximumLoginAttempts = 15  // Error: Cannot assign to value

Variables with var

Variables are declared with var and can be modified after initialization.

var currentLoginAttempt = 0
currentLoginAttempt = 1  // This is fine
currentLoginAttempt += 1 // Now it's 2

Type Annotations

Swift can infer types, but you can also explicitly declare them:

var message: String = "Hello"
var count: Int = 42
let pi: Double = 3.14159

// Type inference works automatically
var inferredMessage = "Hello"  // Swift knows this is a String
var inferredCount = 42         // Swift knows this is an Int

Best Practices

  • Use let by default - it's safer and helps the compiler optimize your code
  • Only use var when you know the value will change
  • Prefer type inference when the type is obvious

Data Types

Swift is a type-safe language with several built-in data types.

Basic Types

Integers

let smallNumber: Int8 = 127        // 8-bit signed integer
let regularNumber: Int = 42        // Platform-native integer (64-bit on modern devices)
let unsignedNumber: UInt = 100     // Unsigned integer (positive only)

// Integer bounds
let minInt = Int.min  // -9223372036854775808
let maxInt = Int.max  // 9223372036854775807

Floating-Point Numbers

let pi: Double = 3.14159265359     // 64-bit floating point (default)
let smallPi: Float = 3.14159       // 32-bit floating point

// Double has more precision, use it by default
let measurement = 42.5             // Inferred as Double

Booleans

let isSwiftFun: Bool = true
let isComplete = false

if isSwiftFun {
    print("Learning Swift!")
}

Strings

let greeting = "Hello, Swift!"
let multiline = """
    This is a multi-line string.
    It can span multiple lines
    and preserves formatting.
    """

// String interpolation
let name = "Alice"
let age = 25
let message = "My name is \(name) and I'm \(age) years old"

// String operations
let length = greeting.count
let uppercase = greeting.uppercased()
let lowercase = greeting.lowercased()

Characters

let letter: Character = "A"
let emoji: Character = "🎉"

Collection Types

Arrays

Ordered collections of values of the same type.

// Creating arrays
var numbers: [Int] = [1, 2, 3, 4, 5]
var names = ["Alice", "Bob", "Charlie"]  // Type inferred as [String]

// Empty array
var emptyArray: [String] = []
var anotherEmpty = [Int]()

// Array operations
numbers.append(6)
numbers.insert(0, at: 0)
numbers.remove(at: 0)
let first = numbers.first  // Optional: Int?
let count = numbers.count

// Accessing elements
let firstNumber = numbers[0]
for number in numbers {
    print(number)
}

Dictionaries

Collections of key-value pairs.

// Creating dictionaries
var ages: [String: Int] = ["Alice": 25, "Bob": 30, "Charlie": 35]
var scores = ["Math": 95, "English": 88]  // Type inferred as [String: Int]

// Empty dictionary
var emptyDict: [String: Int] = [:]
var anotherEmpty = [String: String]()

// Dictionary operations
ages["David"] = 28           // Add or update
ages["Alice"] = 26           // Update
ages.removeValue(forKey: "Bob")

// Accessing values
if let aliceAge = ages["Alice"] {
    print("Alice is \(aliceAge) years old")
}

Sets

Unordered collections of unique values.

var uniqueNumbers: Set<Int> = [1, 2, 3, 4, 5]
var colors: Set = ["Red", "Green", "Blue"]

// Set operations
uniqueNumbers.insert(6)
uniqueNumbers.remove(1)
let contains = uniqueNumbers.contains(3)

// Set operations
let setA: Set = [1, 2, 3, 4]
let setB: Set = [3, 4, 5, 6]

let union = setA.union(setB)              // [1, 2, 3, 4, 5, 6]
let intersection = setA.intersection(setB) // [3, 4]
let difference = setA.subtracting(setB)    // [1, 2]

Tuples

Group multiple values into a single compound value.

let person = ("Alice", 25, "Engineer")
let (name, age, job) = person
print("\(name) is \(age) years old and works as an \(job)")

// Named tuple elements
let employee = (name: "Bob", age: 30, role: "Designer")
print(employee.name)
print(employee.role)

// Function returning tuple
func getCoordinates() -> (x: Int, y: Int) {
    return (10, 20)
}
let position = getCoordinates()
print("x: \(position.x), y: \(position.y)")

Functions

Functions are self-contained chunks of code that perform a specific task.

Basic Function Syntax

func greet() {
    print("Hello, World!")
}

greet()  // Call the function

Functions with Parameters

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

greet(name: "Alice")

// Multiple parameters
func greet(name: String, age: Int) {
    print("Hello, \(name)! You are \(age) years old.")
}

greet(name: "Bob", age: 30)

Argument Labels

// External and internal parameter names
func greet(to name: String, from hometown: String) {
    print("Hello, \(name) from \(hometown)!")
}

greet(to: "Alice", from: "San Francisco")

// Omitting argument labels with underscore
func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

let sum = add(5, 3)  // No need for argument labels

Return Values

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

let result = add(a: 5, b: 3)

// Implicit return for single-expression functions
func multiply(a: Int, b: Int) -> Int {
    a * b  // No 'return' needed
}

Multiple Return Values

func minMax(array: [Int]) -> (min: Int, max: Int) {
    var currentMin = array[0]
    var currentMax = array[0]
    
    for value in array {
        if value < currentMin {
            currentMin = value
        }
        if value > currentMax {
            currentMax = value
        }
    }
    
    return (currentMin, currentMax)
}

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("Min: \(bounds.min), Max: \(bounds.max)")

Default Parameter Values

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

greet(name: "Alice")                    // Uses default "Hello"
greet(name: "Bob", greeting: "Hi")      // Uses custom greeting

Variadic Parameters

func sum(numbers: Int...) -> Int {
    var total = 0
    for number in numbers {
        total += number
    }
    return total
}

let result1 = sum(numbers: 1, 2, 3, 4, 5)
let result2 = sum(numbers: 10, 20)

In-Out Parameters

func swap(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

var x = 5
var y = 10
swap(&x, &y)
print("x: \(x), y: \(y)")  // x: 10, y: 5

Optionals

Optionals handle the absence of a value. They represent either a value or nil.

Declaring Optionals

var optionalName: String? = "Alice"
var optionalAge: Int? = nil

// Optional is like a box that either contains a value or is empty

Unwrapping Optionals

Force Unwrapping (Use with Caution!)

let name: String? = "Alice"
print(name!)  // Force unwrap with ! - CRASHES if nil!

// Only use when you're 100% sure the value exists

Optional Binding (Preferred)

let optionalName: String? = "Alice"

if let name = optionalName {
    print("Hello, \(name)!")
} else {
    print("No name provided")
}

// Multiple optional bindings
let optionalAge: Int? = 25
if let name = optionalName, let age = optionalAge {
    print("\(name) is \(age) years old")
}

Guard Statements

func greet(name: String?) {
    guard let name = name else {
        print("No name provided")
        return
    }
    
    // name is available for the rest of the function
    print("Hello, \(name)!")
}

Nil-Coalescing Operator

let optionalName: String? = nil
let name = optionalName ?? "Guest"  // Use "Guest" if nil
print("Hello, \(name)!")  // Hello, Guest!

// Useful for providing defaults
let userAge: Int? = nil
let age = userAge ?? 18

Optional Chaining

struct Person {
    var name: String
    var address: Address?
}

struct Address {
    var street: String
    var city: String
}

let person = Person(name: "Alice", address: nil)

// Optional chaining with ?
let city = person.address?.city  // Returns nil without crashing
print(city ?? "No city")

// Chaining multiple optionals
let streetLength = person.address?.street.count  // Returns Optional<Int>

Implicitly Unwrapped Optionals

// Use when you know a value will exist after initialization
var assumedString: String! = "Hello"

// Can be used like a non-optional
let greeting = assumedString  // No need to unwrap

// But it's still an optional internally
if assumedString != nil {
    print(assumedString!)
}

// Common in UIKit/SwiftUI outlets
// @IBOutlet var label: UILabel!

Optional Map and FlatMap

let optionalNumber: Int? = 5

// Map transforms the value if it exists
let doubled = optionalNumber.map { $0 * 2 }  // Optional(10)

// FlatMap is useful for chaining optional operations
let result = optionalNumber.flatMap { value -> Int? in
    return value > 0 ? value * 2 : nil
}

Control Flow

Swift provides several ways to control the flow of your code.

If Statements

let temperature = 72

if temperature < 32 {
    print("It's freezing!")
} else if temperature < 50 {
    print("It's cold!")
} else if temperature < 75 {
    print("Nice weather!")
} else {
    print("It's hot!")
}

// No parentheses needed around conditions
// Braces are required even for single-line statements

Switch Statements

Swift's switch statements are very powerful and must be exhaustive.

let character = "a"

switch character {
case "a":
    print("The first letter")
case "z":
    print("The last letter")
default:
    print("Some other letter")
}

// No implicit fallthrough (no need for 'break')
// Multiple cases
switch character {
case "a", "e", "i", "o", "u":
    print("Vowel")
default:
    print("Consonant")
}

// Range matching
let age = 25
switch age {
case 0..<18:
    print("Minor")
case 18..<65:
    print("Adult")
case 65...:
    print("Senior")
default:
    break
}

// Tuple matching
let point = (1, 1)
switch point {
case (0, 0):
    print("Origin")
case (_, 0):
    print("On x-axis")
case (0, _):
    print("On y-axis")
case (-2...2, -2...2):
    print("Inside the box")
default:
    print("Outside the box")
}

// Value binding
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("On x-axis at x = \(x)")
case (0, let y):
    print("On y-axis at y = \(y)")
case let (x, y):
    print("At (\(x), \(y))")
}

// Where clause
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    print("On the line x == y")
case let (x, y) where x == -y:
    print("On the line x == -y")
case let (x, y):
    print("Just at (\(x), \(y))")
}

For Loops

// Range-based for loops
for i in 1...5 {
    print(i)  // 1, 2, 3, 4, 5
}

for i in 1..<5 {
    print(i)  // 1, 2, 3, 4
}

// Array iteration
let names = ["Alice", "Bob", "Charlie"]
for name in names {
    print("Hello, \(name)!")
}

// Dictionary iteration
let ages = ["Alice": 25, "Bob": 30]
for (name, age) in ages {
    print("\(name) is \(age) years old")
}

// Enumerated iteration (with index)
for (index, name) in names.enumerated() {
    print("\(index + 1). \(name)")
}

// Stride for custom steps
for i in stride(from: 0, to: 10, by: 2) {
    print(i)  // 0, 2, 4, 6, 8
}

// Ignoring values with underscore
for _ in 1...3 {
    print("Repeat this 3 times")
}

While Loops

var count = 0
while count < 5 {
    print(count)
    count += 1
}

// Repeat-while (like do-while in other languages)
var number = 0
repeat {
    print(number)
    number += 1
} while number < 5

Control Transfer Statements

Continue

for i in 1...10 {
    if i % 2 == 0 {
        continue  // Skip even numbers
    }
    print(i)  // Only prints odd numbers
}

Break

for i in 1...10 {
    if i == 5 {
        break  // Exit loop when i equals 5
    }
    print(i)  // 1, 2, 3, 4
}

Labeled Statements

outerLoop: for i in 1...3 {
    innerLoop: for j in 1...3 {
        if i == 2 && j == 2 {
            break outerLoop  // Break out of outer loop
        }
        print("i: \(i), j: \(j)")
    }
}

Guard Statements

func processNumber(_ number: Int?) {
    guard let num = number else {
        print("No number provided")
        return
    }
    
    guard num > 0 else {
        print("Number must be positive")
        return
    }
    
    // num is available here and guaranteed to be positive
    print("Processing: \(num)")
}

Structs

Structs are value types that encapsulate related properties and behaviors.

Basic Struct Definition

struct Person {
    var name: String
    var age: Int
}

// Creating instances
var person1 = Person(name: "Alice", age: 25)
var person2 = Person(name: "Bob", age: 30)

// Accessing properties
print(person1.name)
person1.age = 26  // Modify property

Memberwise Initializers

// Swift automatically provides a memberwise initializer
struct Rectangle {
    var width: Double
    var height: Double
}

let rect = Rectangle(width: 10.0, height: 5.0)

Computed Properties

struct Rectangle {
    var width: Double
    var height: Double
    
    // Computed property
    var area: Double {
        return width * height
    }
    
    // Getter and setter
    var perimeter: Double {
        get {
            return 2 * (width + height)
        }
        set {
            // newValue is the default name for the new value
            let side = newValue / 4
            width = side
            height = side
        }
    }
}

var rect = Rectangle(width: 10.0, height: 5.0)
print(rect.area)  // 50.0
rect.perimeter = 40  // Sets width and height to 10

Property Observers

struct StepCounter {
    var totalSteps: Int = 0 {
        willSet {
            print("About to set totalSteps to \(newValue)")
        }
        didSet {
            if totalSteps > oldValue {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}

var counter = StepCounter()
counter.totalSteps = 100
counter.totalSteps = 150

Methods

struct Counter {
    var count: Int = 0
    
    // Instance method
    func describe() {
        print("Current count: \(count)")
    }
    
    // Mutating method (required for modifying properties)
    mutating func increment() {
        count += 1
    }
    
    mutating func increment(by amount: Int) {
        count += amount
    }
    
    mutating func reset() {
        count = 0
    }
}

var counter = Counter()
counter.increment()
counter.increment(by: 5)
counter.describe()  // Current count: 6
counter.reset()

Static Properties and Methods

struct MathHelper {
    static let pi = 3.14159
    static let e = 2.71828
    
    static func degreesToRadians(_ degrees: Double) -> Double {
        return degrees * pi / 180
    }
    
    static func radiansToDegrees(_ radians: Double) -> Double {
        return radians * 180 / pi
    }
}

// Access without creating an instance
print(MathHelper.pi)
let radians = MathHelper.degreesToRadians(90)

Initializers

struct Temperature {
    var celsius: Double
    
    // Custom initializer
    init(celsius: Double) {
        self.celsius = celsius
    }
    
    init(fahrenheit: Double) {
        self.celsius = (fahrenheit - 32) / 1.8
    }
    
    init(kelvin: Double) {
        self.celsius = kelvin - 273.15
    }
}

let temp1 = Temperature(celsius: 25)
let temp2 = Temperature(fahrenheit: 77)
let temp3 = Temperature(kelvin: 298.15)

Value Type Semantics

struct Point {
    var x: Int
    var y: Int
}

var point1 = Point(x: 10, y: 20)
var point2 = point1  // Creates a copy

point2.x = 30

print(point1.x)  // 10 (unchanged)
print(point2.x)  // 30 (modified)

// Structs are copied, not referenced

Structs vs Classes

// Struct (value type)
struct StructPoint {
    var x: Int
    var y: Int
}

// Class (reference type)
class ClassPoint {
    var x: Int
    var y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

// Struct behavior
var sp1 = StructPoint(x: 10, y: 20)
var sp2 = sp1
sp2.x = 30
print(sp1.x)  // 10 (independent copy)

// Class behavior
var cp1 = ClassPoint(x: 10, y: 20)
var cp2 = cp1
cp2.x = 30
print(cp1.x)  // 30 (shared reference)

When to Use Structs

Use structs when:

  • You want value semantics (copying instead of referencing)
  • The data is relatively small and simple
  • You don't need inheritance
  • You want thread safety by default

Examples: geometric shapes, ranges, dates, simple models

Nested Types

struct Player {
    enum Rank: Int {
        case rookie = 1
        case intermediate
        case advanced
        case expert
    }
    
    var name: String
    var rank: Rank
}

let player = Player(name: "Alice", rank: .expert)
print(player.rank.rawValue)  // 4

Extensions

struct Circle {
    var radius: Double
}

// Extend functionality without modifying original definition
extension Circle {
    var diameter: Double {
        return radius * 2
    }
    
    var circumference: Double {
        return 2 * .pi * radius
    }
    
    func area() -> Double {
        return .pi * radius * radius
    }
}

let circle = Circle(radius: 5.0)
print(circle.diameter)       // 10.0
print(circle.circumference)  // 31.415...
print(circle.area())         // 78.539...

Best Practices Summary

  1. Use let over var - Prefer immutability when possible
  2. Leverage type inference - Let Swift infer types when obvious
  3. Use optionals properly - Prefer optional binding over force unwrapping
  4. Choose structs by default - Use classes only when you need reference semantics
  5. Use guard for early exits - Makes code more readable
  6. Leverage computed properties - Better than separate getter methods
  7. Use meaningful names - Clear, descriptive names improve code readability
  8. Keep functions focused - Each function should do one thing well
  9. Use extensions - Organize code and add functionality to existing types
  10. Take advantage of Swift's type safety - Let the compiler catch errors

Next Steps

Now that you understand the basics of Swift, consider exploring:

  • Classes and Inheritance - Object-oriented programming in Swift
  • Protocols and Extensions - Protocol-oriented programming
  • Error Handling - Using throw, try, and catch
  • Closures - Anonymous functions and capturing values
  • Generics - Writing flexible, reusable code
  • Concurrency - Modern async/await patterns
  • SwiftUI - Building user interfaces
  • Combine - Reactive programming framework

Happy coding with Swift! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment