Data Types

Swift is a statically typed language: every value has a type, fixed at compile time. Types fall into two families — value types (copied on assignment) and reference types (shared by reference) — and that distinction shapes how you model data.

This lesson surveys the built-in types, the value/reference split, how to convert between types, and two convenient tools: tuples and type aliases.

Built-in Types

The standard library ships a small set of fundamental types that cover almost every numeric and textual need. Learn their names and sizes, and the rest of the language becomes predictable.

Integers

Integers come in signed and unsigned forms at 8, 16, 32, and 64 bits. Int is the recommended general-purpose type: it matches the machine word size (64-bit on modern Apple and Linux platforms), so it is fast and never accidental.

let score = 42                    // inferred as Int
let big: UInt64 = 18_000_000_000  // underscores do not change the value
let byte: UInt8 = 255             // range 0…255; 256 would not compile

print(Int.min, Int.max)           // platform word-size bounds
print(byte, big, score)           // prints: 255 18000000000 42

Use a sized type such as UInt8 only when a fixed width matters — for binary formats, hardware registers, or an API that demands it. Overflow is a hard error by default, not a silent wrap.

Floating-Point Numbers

Swift offers Double (64-bit) and Float (32-bit). Decimal literals default to Double; prefer it unless memory or hardware constraints force a 32-bit value.

let price = 19.99                 // inferred as Double (the default)
let ratio: Float = 0.5            // 32-bit: less precision, half the memory

let sum = 0.1 + 0.2
print(sum)                        // prints: 0.30000000000000004 — binary rounding
print(price, ratio)               // prints: 19.99 0.5

Booleans

Bool has exactly two values, true and false. Swift does not treat numbers or optionals as booleans, so conditions must be genuinely logical expressions.

let isReady = true                // Bool
let isEmpty = !isReady            // logical NOT
if isReady && !isEmpty {
    print("ready and not empty")  // prints when both conditions hold
}

Characters

Character holds one user-perceived character — an extended grapheme cluster — which may be several Unicode scalars long, as with emoji or accented letters.

let letter: Character = "A"       // one grapheme cluster
let emoji: Character = "🐦"        // one character, several UTF-8 bytes

print(letter, emoji)              // prints: A 🐦

Value vs Reference Types

This is the single most important type distinction in Swift. A value type is copied on assignment; a reference type shares one underlying object, so every name sees the same mutations.

Structs (Value Types)

Int, Double, Bool, String, arrays, dictionaries, and sets are all value types, as is anything you declare with struct. Copying is cheap for small types and is handled by the compiler.

struct Point { var x = 0; var y = 0 }

var a = Point(x: 1, y: 2)
var b = a                    // b is a COPY — value semantics
b.x = 99                     // changing b cannot affect a
print(a.x, b.x)              // prints: 1 99

Classes (Reference Types)

A class instance lives on the heap and is reached through a reference. Assigning it copies the reference, not the object, so both names point at the same data.

class Counter { var value = 0 }

let c1 = Counter()
let c2 = c1                  // c2 refers to the SAME object
c2.value += 1
print(c1.value, c2.value)    // prints: 1 1 — one object, two names

Reach for a struct by default; choose a class only when you need shared mutable identity or inheritance.

Type Conversion

Swift performs no implicit numeric conversion. Mixing an Int and a Double in one arithmetic expression is a compile error, which prevents an entire class of silent precision bugs.

Numeric Conversion

Convert by calling the target type's initializer. Converting to a narrower type truncates toward zero, so make that intent explicit in your code.

let i = 7
let d = Double(i)            // Int → Double, explicit
let back = Int(3.9)          // Double → Int TRUNCATES toward zero: 3

// let bad = i + 1.5         // ❌ error: Int and Double do not mix
let good = Double(i) + 1.5   // 8.5 — conversion made the intent visible
print(d, back, good)         // prints: 7.0 3 8.5

String Conversion

Converting to String always succeeds; converting from text returns an optional, because the text may not represent a valid number.

let n = 42
let text = String(n)          // 42 → "42"
let parsed = Int("7")         // "7" → Optional(7); nil if the text is invalid
let words = [1, 2, 3].map(String.init).joined(separator: ",")

print(text, parsed ?? 0, words)   // prints: 42 7 1,2,3

Tuples

A tuple groups several values of possibly different types into a single compound value. It is a lightweight alternative to defining a struct when the grouping is local and temporary.

Named Tuples

Naming the elements turns a tuple into a readable, self-documenting record that is still just a value.

let user = (name: "Ada", age: 36)     // named tuple
print(user.name, user.age)            // prints: Ada 36

// tuples compare with == when they have the same shape
let same = (name: "Ada", age: 36) == user
print(same)                           // prints: true

Decomposition

You can unpack a tuple into separate constants in one statement, using an underscore to discard the parts you do not need.

let user = (name: "Ada", age: 36)

let (name, age) = user                // destructure both elements
print(name, age)                     // prints: Ada 36

let (_, onlyAge) = user               // ignore the first element
print(onlyAge)                       // prints: 36

Type Aliases

A type alias gives an existing type a second, more meaningful name. It creates no new type — the alias and the original are fully interchangeable, so it is purely a documentation and readability tool.

typealias

Use typealias when a raw type would be ambiguous in context, or when a compound type is too long to repeat.

typealias UserID = Int              // a name that documents intent
typealias Pair = (Int, Int)         // aliases work for compound types too

let id: UserID = 1001
let coords: Pair = (3, 4)           // Pair is just (Int, Int)
print(id, coords.0)                 // prints: 1001 3

Type Safety & Optionals

The compiler refuses to let you use a value of the wrong type — and, crucially, refuses to let you use a value that might be absent. That second promise is enforced by optionals, the feature that most distinguishes Swift from C-family languages.

Inference Revisited

Inference works only from an initial value or an annotation; it never guesses across types. A decimal point is enough to select Double over Int.

let count = 3              // Int
let price = 3.0            // Double — the decimal point changes the type
let items = ["a", "b"]     // [String] (an array of String)
print(type(of: count), type(of: price), type(of: items))

Optionals — a First Look

An optional means "a value of type T, or nothing at all", written T?. The compiler will not let you use the value until you prove it exists, by defaulting it or by binding it.

let nickname: String? = "Ada"      // may hold a String, or nil
print(nickname ?? "no nickname")   // nil-coalescing supplies a default

let missing: Int? = nil
if let value = missing {           // optional binding: runs only if non-nil
    print(value)
} else {
    print("nothing to show")       // this branch runs
}

Common Pitfalls

Integer Overflow

Swift traps on overflow instead of wrapping silently. When wrapping is genuinely intended, use the overflow operator family &+, &-, and &*.

let max = UInt8.max          // 255
// let boom = max + 1        // ❌ runtime trap: arithmetic overflow
let wrapped = max &+ 1       // wrapping add: wraps to 0 on purpose
print(wrapped)               // prints: 0

Floating-Point Precision

Binary floating point cannot represent decimal fractions exactly, so equality tests on computed decimals are unreliable. Compare with a tolerance instead.

let a = 0.1 + 0.2
print(a == 0.3)                    // prints: false — inherent rounding
print(abs(a - 0.3) < 0.000_001)    // prints: true — compare with a tolerance

You now have a map of Swift's type system. Next, put strings and characters to work in Strings, then group values in Collections.