Strings and Characters

In nearly every programming language, text processing revolves around two fundamental concepts: strings and characters. A string is an ordered collection of characters, while a character is the smallest perceptible unit that makes up that text. This article uses Swift as a practical example to explore how strings and characters are defined, created, manipulated, and why Unicode details matter.
Swift’s String type is a value type. Creating a string is straightforward:

let greeting = "Hello"          // string literal
var empty = ""                  // empty string literal
var another = String()          // initializer syntax

Mutability is controlled by the declaration keyword:

  • A string declared with let is immutable and cannot be modified after initialization.
  • A string declared with var is mutable and can be appended to or have its contents replaced.
var message = "Hello"
message += ", World"    // valid: message is now "Hello, World"

Because strings are value types, assigning a string to a new variable or passing it to a function creates a copy. Changes to the copy do not affect the original.

The Character Type

Character represents a single extended grapheme cluster—essentially one user-perceived character. If you write a literal without an explicit type annotation, Swift infers it as String, not Character:

let letter: Character = "A"
let emoji: Character = "😀"
let invalid: Character = "ab"   // compile error: a character literal must contain exactly one character

This distinction matters when you need to work with individual elements rather than the whole string.

Iteration and Counting

A Swift String is itself a collection of Character values, so you can iterate over it directly with a for-in loop:

for char in "Hi" {
    print(char)
}

To get the number of characters, use the count property:

let text = "Hello, world"
print(text.count)   // 12

String Interpolation and Concatenation

String interpolation is the most common way to build dynamic text. Use the \(...) syntax to embed expressions:

let language = "Swift"
let version = 5.9
let info = "Language: \(language), version: \(version)"

For concatenation, you can use the + operator, the += compound assignment, or the append(_:) method:

var phrase = "Hello"
phrase.append(",")        // append a single character
phrase += " World"        // append an entire string

Unicode and Grapheme Clusters

Swift strings are built on Unicode. What a user perceives as a single character may be composed of multiple Unicode scalars. Take the letter "é" as an example. It can be represented in two ways:

let precomposed: Character = "é"            // single scalar U+00E9
let decomposed: Character = "e\u{301}"      // letter e plus combining acute accent

print(precomposed == decomposed)   // true

Swift compares and counts strings by grapheme cluster rather than by raw byte or code unit. Consequently, these two representations are treated as the same character. This behavior is especially important when working with emoji, accented Latin letters, CJK ideographs, or any text where visual appearance does not map one-to-one to underlying storage. The count property therefore returns the number of characters a user would actually see, not the number of UTF-16 code units or bytes.

Common String Operations

Everyday development relies on a small set of high-frequency APIs:

  • Emptiness check: str.isEmpty
  • Prefix and suffix checks: str.hasPrefix("He"), str.hasSuffix("lo")
  • Case conversion: str.uppercased(), str.lowercased()
  • First and last element access: str.first, str.last
  • Index-based access: str[str.startIndex]
  • Insertion and removal: insert(_:at:), remove(at:)
var word = "Hello"
word.insert("!", at: word.endIndex)          // "Hello!"
word.remove(at: word.index(before: word.endIndex))  // back to "Hello"

String Indices and Index Arithmetic

Because a string may contain multi-byte characters, you cannot use an integer offset to index into it directly. Instead, Swift uses String.Index values and provides methods such as startIndex, endIndex, index(after:), and index(before:) for traversal. This is one of the most common stumbling blocks for newcomers coming from languages where str[0] is valid.

let firstIndex = word.startIndex
let secondIndex = word.index(after: firstIndex)
let secondChar = word[secondIndex]

Attempting to use an index outside the valid range, or an index derived from a different string, triggers a runtime error. Always obtain indices from the string you intend to inspect.

Summary

Strings are ordered collections of characters, and characters are the basic building blocks of text. Swift’s String is defined by three key traits: it is a value type, so copies are independent; it is a collection of Character values, making direct iteration natural; and it operates on Unicode grapheme clusters, which keeps text semantics correct across scripts and symbols. Mastering interpolation, concatenation, indexing, and prefix/suffix checks—along with an understanding of grapheme clusters—prepares you for the vast majority of text-processing tasks.