Data Types
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.