The Basics

This chapter introduces the building blocks you use in every line of Swift: how to name and store values, the fundamental numeric and Boolean types, and the safety features — type inference, optionals, assertions — that catch mistakes before they ship. Master these and the rest of the language slots neatly into place.

Constants and variables

A constant is a name bound to a value that never changes; a variable is a name whose value you can change later. Declare constants with let and variables with var. Prefer let — it documents intent and lets the compiler optimize and warn you about accidental mutation.

let maximumLoginAttempts = 3
var currentLoginAttempt = 0

currentLoginAttempt += 1   // fine: currentLoginAttempt is a var
// maximumLoginAttempts = 4  // error: cannot assign to a let constant

You can declare several constants or variables on one line, separated by commas:

var x = 0.0, y = 0.0, z = 0.0

Type annotations

Most of the time Swift infers the type for you, but you can write a type annotation after the name to be explicit — useful when there's no initial value, or when you want a type other than the inferred one.

var welcomeMessage: String
welcomeMessage = "Hello"

var red, green, blue: Double

Names can use almost any Unicode character, including emoji, but cannot contain whitespace, mathematical symbols, or start with a number. To use a reserved word as a name, wrap it in backticks: let `class` = "year 2026".

Comments and semicolons

Comments are ignored by the compiler. Swift supports single-line // comments and block /* ... */ comments, and block comments can nest — handy for commenting out code that already contains a comment.

// This is a single-line comment.

/* This is a block comment
   /* and this nested one is fine */
   so the outer comment still closes correctly. */

Semicolons are optional at the end of a statement. They're only required when you want to write multiple statements on a single line:

let cat = "🐱"; print(cat)   // → 🐱

Integers, floating-point, and Bool

Int is the go-to integer type; it matches the platform's word size (64-bit on every current Apple platform). Swift also offers sized variants — Int8, UInt16, Int32, UInt64, and so on — but you should reach for Int unless you specifically need a fixed width or an unsigned type. Access bounds with .min and .max.

let minValue = UInt8.min   // → 0
let maxValue = UInt8.max   // → 255

For decimal numbers use Double (64-bit, ~15 significant digits) or Float (32-bit, ~6 digits). When inference must choose, it always picks Double. Bool holds true or false.

let pi = 3.14159          // inferred as Double
let temperature: Float = 98.6
let orangesAreOrange = true   // inferred as Bool

Type safety and type inference

Swift is a type-safe language: it checks types at compile time and flags mismatches as errors. This works hand-in-hand with type inference, which deduces a value's type from how you initialize it — so you rarely have to write types out, yet every value still has a precise, checked type.

let meaningOfLife = 42        // inferred Int
let anotherPi = 3 + 0.14159   // inferred Double

Because of type safety, Swift never converts between numeric types implicitly. You must convert explicitly, by constructing the destination type:

let three = 3
let pointOneFour = 0.14
let total = Double(three) + pointOneFour   // → 3.14

Numeric literals and type aliases

Integer literals can be written in decimal, binary (0b), octal (0o), or hexadecimal (0x). You can use underscores as visual separators and pad with leading zeros — they don't change the value. Floating-point literals support exponents (e for decimal, p for hex).

let decimal = 1_000_000
let binary = 0b1010      // → 10
let hex = 0xFF           // → 255
let oneMillion = 1e6      // → 1000000.0
let small = 1.5e-2        // → 0.015

A type alias gives an existing type an alternative name, which can make code read more meaningfully:

typealias AudioSample = UInt16
let maxAmplitude = AudioSample.max   // → 65535

Tuples

A tuple groups multiple values into a single compound value. The values can be of different types, and you can name the elements. Tuples are great for returning several values from a function without defining a whole struct.

let http404Error = (404, "Not Found")

// Decompose into separate constants
let (statusCode, statusMessage) = http404Error
print("Code: \(statusCode), Message: \(statusMessage)")
// → Code: 404, Message: Not Found

// Access by index, or use named elements
print(http404Error.0)   // → 404

let http200 = (statusCode: 200, description: "OK")
print(http200.description)   // → OK

Use the underscore _ to ignore parts you don't need: let (justTheCode, _) = http404Error.

Optionals

An optional represents a value that may be absent. You write an optional by appending ? to a type. An optional either contains a value of that type, or it contains nil — the absence of a value. This is one of Swift's defining safety features: a non-optional type can never be nil, so you only handle absence where it's genuinely possible.

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is of type Int? (an optional Int)
// because the string might not be a valid number

var serverResponseCode: Int? = 404
serverResponseCode = nil   // now it has no value

Watch out: Force-unwrapping with ! (e.g. convertedNumber!) crashes at runtime if the optional is nil. Only force-unwrap when you can guarantee a value exists.

Optional binding: if let and guard let

Optional binding safely unwraps an optional into a temporary constant if it has a value. With if let the unwrapped value is available inside the if body. Swift lets you use the shorthand if let name when the new name matches the optional's name.

if let number = Int(possibleNumber) {
    print("The string holds the number \(number).")
} else {
    print("That wasn't a number.")
}
// → The string holds the number 123.

// Shorthand: same name, value is unwrapped in place
let name: String? = "Ada"
if let name {
    print("Hi, \(name)")   // → Hi, Ada
}

guard let unwraps an optional and requires you to exit the current scope if it's nil. Crucially, the unwrapped value stays in scope for the rest of the function — perfect for early-exit validation at the top of a function.

func greet(_ maybeName: String?) {
    guard let name = maybeName else {
        print("No name supplied.")
        return
    }
    // name is a non-optional String from here on
    print("Hello, \(name)!")
}
greet("Grace")   // → Hello, Grace!
greet(nil)       // → No name supplied.

You can bind multiple optionals and add Boolean conditions in one statement, separated by commas:

if let first = Int("4"), let second = Int("42"), first < second {
    print("\(first) < \(second)")   // → 4 < 42
}

Implicitly unwrapped optionals

Sometimes you know an optional will always have a value after it's first set — for instance, a property wired up during initialization. An implicitly unwrapped optional, written with ! instead of ?, can be used without unwrapping while still permitting nil behind the scenes. Use it sparingly.

let assumedString: String! = "An implicitly unwrapped optional."
let implicitString: String = assumedString   // no ! needed

Error handling intro

Optionals model the absence of a value; error handling models an operation that can fail and report why. A function that can throw is marked throws; you call it with try inside a do/catch block. This is a first look — the Error Handling chapter goes deep.

enum SandwichError: Error {
    case outOfBread
}

func makeSandwich() throws {
    throw SandwichError.outOfBread
}

do {
    try makeSandwich()
} catch SandwichError.outOfBread {
    print("Buy more bread.")   // → Buy more bread.
}

Assertions and preconditions

When you want to verify an assumption at runtime, use an assertion or a precondition. Both stop execution if their condition is false and print a message. The difference: assertions are checked only in debug builds, while preconditions are checked in production builds too. Use them to catch programmer mistakes, not to validate ordinary user input (use error handling for that).

let age = -3
assert(age >= 0, "Age can't be negative.")

// Checked in release builds too:
func withdraw(_ amount: Int, from balance: Int) {
    precondition(amount <= balance, "Insufficient funds.")
    // ...
}

Use assertionFailure(_:) or preconditionFailure(_:) to indicate a code path that should never be reached, and fatalError(_:) to stop unconditionally in any build configuration.

Tip: Reach for let, prefer non-optional types, and lean on inference. The more the compiler knows at build time, the fewer surprises you get at runtime.

Wrapping up

You now have the core vocabulary of Swift: constants and variables, the fundamental numeric and Boolean types, type safety with inference, tuples for grouping values, optionals for safely modeling absence, a first taste of error handling, and assertions for catching bad assumptions. Next we'll see how to combine these values with operators.