Swift Type Casting

Type casting lets you convert between related types or check a value's runtime type, using the as, as?, and as! operators to match the level of certainty you actually have.

Why Type Casting Exists

Swift is strict about types: an Int and a Double cannot be mixed in the same expression, and a value typed as Any could be hiding any concrete type underneath. Type casting is how you bridge that gap — either by converting a numeric value from one type to another, or by asking a class instance what its actual runtime type is.

Numeric conversion

let wholeNumber: Int = 7
let asDouble = Double(wholeNumber)   // 7.0

let decimal: Double = 9.8
let asInt = Int(decimal)              // 9, truncated, not rounded
Note: Converting a Double to an Int truncates the decimal part rather than rounding it — Int(9.8) produces 9, not 10.

as, as?, and as!

When working with class hierarchies, Swift gives you three casting operators. Plain as performs a cast that's guaranteed to succeed, typically when casting up to a known superclass or protocol. as? is the optional cast — it returns nil instead of crashing if the cast fails, so it's the safest choice when you're not certain of a value's type. as! is the forced cast — it assumes success and crashes at runtime if it's wrong, so it should only be used when you are absolutely certain of the type.

Safe vs forced casting

class Animal {}
class Dog: Animal { func bark() { print("Woof!") } }

let pet: Animal = Dog()

if let dog = pet as? Dog {
    dog.bark() // safe: runs only if the cast succeeds
} else {
    print("Not a dog")
}

let forcedDog = pet as! Dog // crashes here if pet isn't actually a Dog
forcedDog.bark()

Casting with Any and AnyObject

Collections typed as [Any] can hold values of completely different types, and casting is how you recover their real type before using type-specific behavior. This pattern shows up often when working with heterogeneous arrays or data parsed from JSON.

Casting inside a loop

let mixedValues: [Any] = [1, "two", 3.0, true]

for value in mixedValues {
    if let number = value as? Int {
        print("Int: \(number)")
    } else if let text = value as? String {
        print("String: \(text)")
    } else {
        print("Other: \(value)")
    }
}
  • as — upcasting that always succeeds, checked at compile time.
  • as? — safe downcasting that returns an optional, nil on failure.
  • as! — forced downcasting that crashes the app if the cast fails.
  • Int(), Double(), String() initializers convert between concrete value types.

Choosing the Right Operator

OperatorResult typeFailure behavior
asNon-optionalCompile-time guarantee, cannot fail
as?OptionalReturns nil at runtime on failure
as!Non-optionalCrashes the app at runtime on failure
Note: As a rule of thumb: reach for as? in production code and treat as! as a deliberate assertion you're making about data you fully control, such as a value you just inserted yourself.

Exercise: Swift Type Casting

What does the as? operator return when a downcast fails?