Type Casting (is, as?)
Reviewed & published by Brayan K
Discover and convert types at runtime: check a type with is, safely downcast with as?, force a cast with as!, and work with the catch-all Any and AnyObject types.
Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • Check an instance's type with is (returns a Bool)
- • Safely downcast with as? (returns an optional)
- • Force a downcast with as! — and know it can crash
- • Use Any and AnyObject for mixed values
- • Downcast inside a switch with case let x as T:
- • Choose the right operator for each situation
1️⃣ Checking Types with is
The is operator answers a yes/no question: is this instance of that type? It returns a Bool, so it's perfect for counting or filtering without converting anything.
// 'is' asks "is this instance of that type?" and gives back a Bool.
class Animal {}
class Dog: Animal { func bark() { print("Woof") } }
class Cat: Animal { func meow() { print("Meow") } }
let pets: [Animal] = [Dog(), Cat(), Dog()]
var dogCount = 0
for pet in pets {
if pet is Dog { // type check -> true / false
dogCount += 1
}
}
print("Dogs: \(dogCount)") // Dogs: 2
print(Dog() is Animal) // true (a Dog IS an Animal)
print(Animal() is Dog) // false (an Animal is not necessarily a Dog)2️⃣ Downcasting with as? and as!
Downcasting converts a general type to a more specific one. The safe form, as?, returns an optional — the value on success, nil on failure — so pair it with if let. The forced form, as!, returns the value directly but crashes if the cast fails, so only use it when you're certain.
// Downcasting turns a general type into a more specific one.
// as? -> conditional: returns an Optional (nil if it can't cast)
// as! -> forced: crashes if it can't cast (use only when CERTAIN)
class Animal {}
class Dog: Animal { func bark() { print("Woof") } }
let something: Animal = Dog()
// Safe: try the cast, handle the nil case.
if let dog = something as? Dog {
dog.bark() // Woof — cast succeeded
} else {
print("Not a dog")
}
// Forced: only because we KNOW 'something' is a Dog here.
let dog2 = something as! Dog
dog2.bark() // Woof
// A failing as? simply yields nil — no crash.
let notACat = something as? Cat
print(notACat as Any) // nil
class Cat: Animal {}3️⃣ Any / AnyObject and Downcasting in a switch
Any can hold a value of any type; AnyObject holds only class instances. To work with a mixed collection, discover each element's real type by downcasting inside a switch using case let x as Type:.
// Any can hold a value of ANY type (Int, String, even a function).
// AnyObject holds only CLASS instances.
let mixed: [Any] = [42, "hello", 3.14, true]
for item in mixed {
// Downcast in a switch with 'case let x as Type:'
switch item {
case let n as Int:
print("Int: \(n)")
case let s as String:
print("String: \(s)")
case let d as Double:
print("Double: \(d)")
case let b as Bool:
print("Bool: \(b)")
default:
print("Something else")
}
}
// Int: 42
// String: hello
// Double: 3.14
// Bool: trueYour turn. Fill in the type-check and conditional-cast operators.
// 🎯 YOUR TURN — fill in each ___ then run it on SwiftFiddle.
let values: [Any] = [10, "swift", 2.5]
for value in values {
// 1) Check if it's a String using the type-check operator.
if value ___ String { // 👉 the 'is' operator
print("Found a string")
}
// 2) Safely cast to Int using the conditional cast operator.
if let number = value ___ Int { // 👉 the 'as?' operator
print("Int doubled: \(number * 2)")
}
}
// ✅ Expected output:
// Int doubled: 20
// Found a stringPro Tips
- 💡 Default to as?. Pair it with if let or guard let to handle the miss.
- 💡 Reserve as! for casts you can prove — a wrong guess crashes instantly.
- 💡 switch + case let x as T is the cleanest way to handle an Any.
- 💡 Prefer protocols/generics over casting when you can — needing lots of casts often hints at a design you can simplify.
Common Errors (and the fix)
- "Could not cast value of type ... " crash: an as! cast failed. Switch to as? and handle nil.
- Forgetting the result of as? is optional: bind it with if let before using it.
- "'is' test is always true": a warning that the check is redundant — the value is already that type.
- Using AnyObject for a struct/Int: value types need Any; AnyObject is class-only.
📋 Quick Reference
| Operator | Code | Returns |
|---|---|---|
| Type check | x is Dog | Bool |
| Safe cast | x as? Dog | Dog? (nil on fail) |
| Forced cast | x as! Dog | Dog (crash on fail) |
| Upcast | dog as Animal | always succeeds |
| Mixed values | [Any] | any type |
| In switch | case let n as Int: | binds n |
Mini-Challenge: Shape Describer
No blanks this time — just a brief and an outline. Use a switch with downcasting cases to describe each shape.
// 🎯 MINI-CHALLENGE: shape describer
// 1. Make classes: Shape {}, Circle: Shape { let radius = 5 },
// Square: Shape { let side = 4 }
// 2. Put a Circle and a Square into let shapes: [Shape] = [...]
// 3. Loop over shapes and use a switch with 'case let c as Circle:' etc.
// to print "Circle r=5" or "Square s=4".
// 4. Add a default branch printing "Unknown shape".
//
// ✅ Expected:
// Circle r=5
// Square s=4
// your code here
Practice quiz
What does the 'is' operator check?
- Whether a value can be cast and returns the cast value
- Whether two values are equal
- Whether an instance is of a certain type, returning a Bool
- Whether a value is nil
Answer: Whether an instance is of a certain type, returning a Bool. 'is' is the type-check operator: it returns true or false depending on the instance's type.
What does the 'as?' operator return?
- An optional — the cast value if it succeeds, or nil if it fails
- The cast value, or a crash on failure
- Always nil
- A Bool
Answer: An optional — the cast value if it succeeds, or nil if it fails. 'as?' is the conditional cast; it returns an optional that is nil when the cast can't succeed.
What happens with 'as!' if the cast fails at runtime?
- It returns nil
- It returns false
- It returns the original value
- It crashes the program
Answer: It crashes the program. 'as!' is the forced cast — it crashes if the downcast is not actually possible.
Which cast is safest to use when you are not certain of the type?
- as!
- as?
- is
- as
Answer: as?. Use 'as?' (conditional cast) when unsure; it returns nil instead of crashing.
What is the difference between Any and AnyObject?
- Any can hold any type including value types; AnyObject only holds class instances
- They are identical
- AnyObject holds any type; Any only holds classes
- Any only holds Ints
Answer: Any can hold any type including value types; AnyObject only holds class instances. Any represents an instance of any type at all; AnyObject represents an instance of any class type.
When downcasting in a switch statement, which keyword pattern is used?
- is only
- case x is SomeType:
- case let x as SomeType:
- cast SomeType
Answer: case let x as SomeType:. In a switch you write 'case let x as SomeType:' to bind the downcast value.
Is 'as' (without ? or !) ever used, and for what?
- Never
- For guaranteed conversions like upcasting or bridging that always succeed
- Only for optionals
- Only in switch
Answer: For guaranteed conversions like upcasting or bridging that always succeed. Plain 'as' is for casts the compiler knows always succeed, such as upcasting to a superclass.
Given 'let things: [Any] = [1, "hi", 3.0]', how do you safely treat an element as a String?
- thing as String
- thing as! Int
- thing is String only
- if let s = thing as? String { }
Answer: if let s = thing as? String { }. 'as?' with optional binding safely extracts the String when the element actually is one.
What does 'value is String' evaluate to when value holds an Int?
- true
- false
- nil
- It crashes
Answer: false. 'is' returns false when the instance is not of the checked type.
Why use 'as?' over 'as!' for values from a heterogeneous collection?
- as? is faster
- as! returns nil
- as? lets you handle the non-matching case gracefully instead of crashing
- There is no difference
Answer: as? lets you handle the non-matching case gracefully instead of crashing. With mixed types you can't guarantee the cast, so as? avoids the crash that as! would cause on a mismatch.
Continue this course
- Previous: Optional Chaining
- Next: Access Control — Control visibility with open, public, internal, fileprivate and private
Frequently asked questions
What is the difference between is, as?, and as!?
They serve three related jobs. 'is' is a pure check: it returns a Bool telling you whether an instance is of a given type. 'as?' is a conditional (safe) cast: it returns an optional that holds the cast value on success or nil on failure. 'as!' is a forced cast: it returns the cast value directly but crashes at runtime if the cast can't succeed. Reach for as? unless you can guarantee the type.
When should I use as! instead of as??
Only when you are absolutely certain the cast will succeed — for example, you just checked with 'is', or the value's real type is known from the surrounding code. If there's any doubt, use as? and handle the nil case, because as! turns a wrong assumption into an immediate crash.
What is the difference between Any and AnyObject?
Any can represent an instance of absolutely any type, including value types like Int and String, structs, enums, and even functions. AnyObject is narrower: it represents an instance of any class type only. Use Any for truly mixed collections; AnyObject mainly appears when bridging with Objective-C or when you specifically need reference types.
How do I downcast inside a switch statement?
Use the pattern 'case let name as SomeType:'. The switch attempts the cast for each case and, on a match, binds the downcast value to 'name' so you can use it with its specific type inside that case. Add a default case to handle anything that doesn't match. This is the cleanest way to handle a value of type Any whose concrete type you need to discover.
What is plain 'as' (without ? or !) used for?
Plain 'as' performs a cast the compiler can prove always succeeds, so it never fails and never returns an optional. The classic examples are upcasting an instance to one of its superclasses or to a protocol it conforms to, and certain guaranteed bridging conversions (like String as NSString in some contexts). Because it's guaranteed, you don't need ? or !.