Swift Strings & Characters: The Complete 2025 Guide
  • July 23, 2025

Swift’s String type is Unicode-correct, performant, and safe—yet it can surprise newcomers because of grapheme clusters, copy-on-write behavior, and value semantics. In this guide you’ll master everything from basic literals to inserting/removing substrings and comparing values—so you can write clean, efficient, and bug-free iOS code.


Table of Contents

  1. Strings and Characters
  2. String Literals
  3. Special Characters in String Literals
  4. Initializing an Empty String
  5. String Mutability
  6. Working with Characters
  7. Concatenating Strings and Characters
  8. String Interpolation
  9. Counting Characters
  10. Accessing and Modifying a String
  11. Inserting and Removing
  12. Comparing Strings
  13. FAQ
  14. Conclusion & Next Steps

Strings and Characters

Swift’s String is a collection of Character values, where each Character represents an extended grapheme cluster (a human-readable symbol that might be composed of many Unicode scalars). This design keeps your UI text and localized content correct by default. Need the low-level scalars? unicodeScalars has you covered, but most app code should stick to String and Character.

Further reading: the official Strings and Characters section in the Swift Language Guide on docs.swift.org.


String Literals

A string literal is a fixed sequence of characters enclosed in double quotes:

let greeting = "Hello, Swift!"

For multi-line text, use triple quotes. Leading whitespace is preserved unless trimmed with a backslash:

let multiline = """
    First line
    Second line
    """

You can also control indentation by placing the closing """ on a new line.


Special Characters in String Literals

Escape sequences let you embed characters that are hard to type or read:

  • \n newline
  • \t tab
  • \" double quote
  • \\ backslash
  • \u{1F680} Unicode scalar (🚀)
let rocket = "Launch\nReady: \u{1F680}"
print(rocket)
// Launch
// Ready: 🚀

Raw string literals (#"..."#) reduce escaping noise. Prefix and suffix the literal with # to treat backslashes and quotes literally unless matched by the same number of # signs:

let raw = #"Path: C:\\Users\\me"#

Initializing an Empty String

Create an empty string with a literal or initializer—both are equivalent:

var empty1 = ""
var empty2 = String()

Use isEmpty to check for emptiness rather than comparing with "".


String Mutability

Strings are value types. Declare with var to mutate, let to keep immutable:

var name = "Mehul"
name += " Valand" // allowed

let fixed = "Swift"
// fixed += " 6" // compile-time error

Behind the scenes, Swift uses copy-on-write: copying is deferred until one copy mutates, keeping performance high.


Working with Characters

Iterate over characters easily:

for ch in "Swift" {
    print(ch)
}

Convert a String to an array of Character or vice versa:

let chars: [Character] = ["S", "w", "i", "f", "t"]
let word = String(chars) // "Swift"

Remember that characters may be composed of multiple scalars (e.g., é vs. e + accent). Counting and indexing are grapheme-aware.


Concatenating Strings and Characters

Use +, +=, or append(_:):

let part1 = "Hello"
let part2 = ", World"
let exclamation: Character = "!"

var message = part1 + part2
message.append(exclamation) // "Hello, World!"

For performance-heavy concatenation, consider String’s reserveCapacity(_:) or StringBuilder-like patterns, but most apps won’t need them.


String Interpolation

Interpolation inserts values right into your literals:

let count = 3
let item = "bug"
let line = "I fixed \(count) \(count == 1 ? item : item + "s")."

Swift 5.5+ lets you customize interpolation by extending String.StringInterpolation. Advanced use cases include formatting dates or numbers with locale-aware style.


Counting Characters

count returns the number of Character values, not bytes nor Unicode scalars:

let cafe = "café" // 4 characters
echo(cafe.count) // 4

Combining marks still count as part of one character. If you need the scalar count: cafe.unicodeScalars.count.


Accessing and Modifying a String

Strings don’t support integer subscripts because grapheme clusters vary in length. Use indices:

let text = "Swift"
let start = text.startIndex
let second = text.index(after: start)
print(text[second]) // 'w'

Use index(_:offsetBy:) to move forward or backward safely, and indices to iterate over positions. Always respect startIndex and endIndex (which is one past the last valid character).


Inserting and Removing

Insert characters or strings at any valid index:

var welcome = "Hello"
welcome.insert("!", at: welcome.endIndex)          // Hello!
welcome.insert(contentsOf: ", Swift", at: welcome.index(before: welcome.endIndex)) // Hello, Swift!

Remove with remove(at:), removeSubrange(_:), or high-level helpers:

welcome.remove(at: welcome.index(before: welcome.endIndex)) // remove '!'
let range = welcome.firstIndex(of: ",")!..<welcome.endIndex
welcome.removeSubrange(range) // back to "Hello"

When working with Substring, convert back to String for long-term storage to free memory.


Comparing Strings

Swift supports value equality (==), lexicographical comparison (<, >) and prefix/suffix checks:

let a = "straße"
let b = "strasse"
print(a == b) // false – different grapheme clusters

"Hello".hasPrefix("He")  // true
"Hello".hasSuffix("lo")   // true

Comparisons are locale-insensitive by default. If you need localized comparison or case folding, use Foundation’s localizedStandardCompare(_:) or lowercased() with a locale.


FAQ

Q1. Why can’t I subscript a Swift String with an Int index?
Because characters are variable-width Unicode grapheme clusters. Integer offsets could split a character in the middle. Use String.Index APIs instead.

Q2. Is String a value or reference type?
It’s a value type with copy-on-write semantics. You get predictable value behavior without unnecessary copies.

Q3. How do I efficiently build large strings?
Repeated concatenation is usually fine. For extreme cases, pre-allocate with reserveCapacity(_:) or append to an array and join.

Q4. What’s the difference between Character and UnicodeScalar?
Character is a human-readable grapheme cluster. UnicodeScalar is a single 21-bit code point. One character may contain multiple scalars.

Q5. How do I compare strings ignoring case or diacritics?
Use Foundation helpers: caseInsensitiveCompare, folding(options: .diacriticInsensitive, locale: ...), or localizedStandardCompare for user-facing text.


Conclusion & Next Steps

Swift’s String API balances correctness and ergonomics: you get Unicode safety without sacrificing speed. Mastering indices, interpolation, and mutation patterns will save you from subtle bugs.

Next steps:


YOU MIGHT ALSO LIKE...
🧭 NavigationKit

NavigationKit is a lightweight library which makes SwiftUI navigation super easy to use. 💻 Installation 📦 Swift Package Manager Using Swift Package Manager, add ...

swiftui-navigation-stack

An alternative SwiftUI NavigationView implementing classic stack-based navigation giving also some more control on animations and programmatic navigation. NavigationStack Installation ...

Stinsen

Simple, powerful and elegant implementation of the Coordinator pattern in SwiftUI. Stinsen is written using 100% SwiftUI which makes it ...

SwiftUI Router

With SwiftUI Router you can power your SwiftUI app with path-based routing. By utilizing a path-based system, navigation in your app becomes ...

FlowStacks

This package takes SwiftUI's familiar and powerful NavigationStack API and gives it superpowers, allowing you to use the same API not just ...