Answer
Core answer
The key difference is this: print uses String(describing:) and debugPrint uses String(reflecting:).
In practice, print aims for a more readable, user-facing representation, while debugPrint asks for a representation that is more useful during debugging. But that does not mean debugPrint is always different or always includes type information. For many values, both functions produce the same output.
1. Sometimes they are exactly the same
For many standard values, there is no visible difference at all:
let count = 42
let ready = true
print(count)
debugPrint(count)
print(ready)
debugPrint(ready)Run compiles and executes on the server; output shows below.
For values like these, both representations are simple and unambiguous, so the output is the same.
2. Strings are a good example of when they differ
Strings often show the difference clearly. print writes the string as a user would normally read it. debugPrint shows the debug form, which keeps quotes and escape sequences visible.
let message = "Hello\nSwift"
print(message)
debugPrint(message)Run compiles and executes on the server; output shows below.
Here, print renders the newline as an actual line break. debugPrint shows the string more literally, which is useful when you want to see hidden characters and escaping behavior.
3. How CustomStringConvertible changes print
If a type conforms to CustomStringConvertible, it provides a custom description. That is the representation print prefers.
If the type does not also provide a separate debug representation, debugPrint falls back to that same description.
struct User: CustomStringConvertible {
let id: Int
var description: String {
"User #\(id)"
}
}
let user = User(id: 7)
print(user)
debugPrint(user)Run compiles and executes on the server; output shows below.
This is an important interview nuance: if a type only customizes description, print and debugPrint can still look identical.
4. How CustomDebugStringConvertible changes debugPrint
If a type also conforms to CustomDebugStringConvertible, it can provide a separate debugDescription. That is what debugPrint prefers.
There is an important nuance here: the fallback works in the other direction too. If a type only provides debugDescription, print can end up showing that text as well. The real difference is preference order, not a hard wall where each function can only ever use one protocol.
struct User: CustomStringConvertible, CustomDebugStringConvertible {
let id: Int
let email: String
var description: String {
"User #\(id)"
}
var debugDescription: String {
"User(id: \(id), email: \(email))"
}
}
let user = User(id: 7, email: "ana@example.com")
print(user)
debugPrint(user)Run compiles and executes on the server; output shows below.
Now the difference is explicit:
print(user)usesdescription, which stays concise.debugPrint(user)usesdebugDescription, which can expose more implementation detail that helps during development.
Interview angle
Walk the answer in this order: print uses String(describing:) -> debugPrint uses String(reflecting:) -> many values print the same way -> strings often show the difference because debug output preserves escapes and quotes -> CustomStringConvertible controls description -> CustomDebugStringConvertible controls debugDescription when you want a separate debugging view.