Top 60 Objective-C Interview Questions (2026)

The 60 Objective-C questions interviewers actually ask, with direct answers, runnable code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced iOS and macOS rounds.

60 questions with answers

What Is Objective-C?

Key Takeaways

  • Objective-C is a strict superset of C that adds Smalltalk-style message passing and a dynamic runtime for object-oriented code.
  • It powered iOS and macOS development for decades and still runs vast production codebases alongside Swift through interoperability.
  • Interviews test the runtime, memory management (retain counting and ARC), and Foundation classes, not just C syntax.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery is evaluated too.

Objective-C is a general-purpose, object-oriented programming language that adds Smalltalk-style messaging to C. Brad Cox and Tom Love designed it in the early 1980s, and Apple made it the primary language for building NeXTSTEP, then Mac OS X, iOS, and every Apple platform through the mid-2010s. Because it's a strict superset of C, any valid C program is valid Objective-C, and you can mix C, C++, and Objective-C in one file. What makes it distinct is the dynamic runtime: method calls are messages resolved at runtime, objects can respond to messages the compiler never saw, and you can inspect and rewrite classes while the program runs. Apple's official Programming with Objective-C guide is the canonical reference for the language and its runtime. In interviews, Objective-C questions probe the runtime, memory management (manual retain counting and ARC), Foundation classes like NSString and NSArray, and patterns like delegation, not memorized C trivia. This page collects the 60 questions that come up most, each with a direct answer and runnable code. Increasingly the first round runs as a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
1984Year Objective-C was designed by Brad Cox and Tom Love
ARCModern memory model since Xcode 4.2

Watch: Objective C Tutorial

Video: Objective C Tutorial (Derek Banas, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your Objective-C certificate.

Jump to quiz

All Questions on This Page

60 questions
Objective-C Interview Questions for Freshers
  1. 1. What is Objective-C and where is it used?
  2. 2. What is message passing, and how does it differ from a method call?
  3. 3. What happens when you send a message to nil?
  4. 4. What is the id type?
  5. 5. What is the difference between the @interface and @implementation of a class?
  6. 6. What is the difference between NSString and NSMutableString?
  7. 7. What are NSArray and NSDictionary?
  8. 8. What does @property do, and what are getters and setters?
  9. 9. What do the strong, weak, copy, and assign property attributes mean?
  10. 10. What is BOOL in Objective-C, and how does it differ from bool?
  11. 11. What is the difference between self and super?
  12. 12. What do alloc and init do, and why are they usually paired?
  13. 13. What is instancetype and why prefer it over id for initializers?
  14. 14. What is the difference between a class method and an instance method?
  15. 15. What is a selector (SEL)?
  16. 16. What is NSLog and what does the %@ format specifier do?
  17. 17. How does inheritance work in Objective-C?
  18. 18. What is the difference between isKindOfClass: and isMemberOfClass:?
  19. 19. What is fast enumeration?
  20. 20. What is the description method and when is it called?
Objective-C Intermediate Interview Questions
  1. 21. What is ARC and how does it work?
  2. 22. In manual reference counting, what do retain, release, and autorelease do?
  3. 23. What is a retain cycle and how do you break it?
  4. 24. What is the delegate pattern and how do you implement it?
  5. 25. How do required and optional protocol methods work?
  6. 26. What is a category and what are its limits?
  7. 27. What is the difference between a category and a class extension?
  8. 28. What are blocks and how do they capture variables?
  9. 29. Why do blocks cause retain cycles, and what is the weakSelf pattern?
  10. 30. What is an autorelease pool and when do you add one manually?
  11. 31. Why declare NSString properties as copy instead of strong?
  12. 32. How do you make a class copyable with NSCopying?
  13. 33. What is the contract between isEqual: and hash?
  14. 34. When do you use NSException versus NSError?
  15. 35. What is Grand Central Dispatch and how do you use it?
  16. 36. Why must UI updates happen on the main thread?
  17. 37. What is the difference between atomic and nonatomic properties?
  18. 38. What is Key-Value Coding (KVC)?
  19. 39. What is Key-Value Observing (KVO)?
  20. 40. How do you implement a thread-safe singleton?
Objective-C Interview Questions for Experienced Developers
  1. 41. What is the Objective-C runtime, and what does it do?
  2. 42. What actually happens when you send a message? Explain objc_msgSend.
  3. 43. How does message forwarding work?
  4. 44. What is method swizzling, and when is it justified?
  5. 45. What are associated objects and why do they exist?
  6. 46. What is the difference between +load and +initialize?
  7. 47. What is a designated initializer, and why does the pattern matter?
  8. 48. What is the difference between weak and unsafe_unretained?
  9. 49. What are tagged pointers?
  10. 50. What is the isa pointer, and what changed with the non-pointer isa?
  11. 51. How do you build a thread-safe reader-writer cache with GCD?
  12. 52. What are the options for locking, and how do they compare?
  13. 53. What is toll-free bridging and how do __bridge casts work?
  14. 54. How do you serialize objects with NSCoding and NSSecureCoding?
  15. 55. How does Objective-C interoperate with Swift?
  16. 56. You get EXC_BAD_ACCESS in an Objective-C app. How do you debug it?
  17. 57. How do you find a memory leak or performance issue with Instruments?
  18. 58. What are the three block storage types, and why do they matter?
  19. 59. Design question: how would you structure a networking layer in Objective-C?
  20. 60. How do you unit test Objective-C code?

Objective-C Interview Questions for Freshers

Freshers20 questions

The fundamentals every entry-level iOS round checks. If any answer here surprises you, that's your study list.

Q1. What is Objective-C and where is it used?

Objective-C is an object-oriented language that adds Smalltalk-style messaging to C. It's a strict superset of C, so any C code compiles as Objective-C, and it was Apple's primary language for building iOS, macOS, watchOS, and tvOS apps before Swift.

You'll still find it across huge production codebases, SDKs, and frameworks, which is why iOS teams hire people who can read and maintain it. Its distinctive traits are a dynamic runtime and message passing rather than direct method calls.

Key point: A one-line definition plus the superset-of-C point and the runtime point is a strong opener. Interviewers use this to hear how you organize an answer.

Q2. What is message passing, and how does it differ from a method call?

In Objective-C you send a message to an object rather than calling a method directly. The runtime looks up which method actually runs at send time, using the object's class and the selector, so the binding is dynamic, not fixed at compile time.

That's why you can send a message an object learns to handle at runtime, and why sending a message to nil is safe. The bracket syntax [object doSomething] is the message send.

objc
// message send: runtime resolves -uppercaseString at send time
NSString *name = @"ada";
NSString *shout = [name uppercaseString];   // @"ADA"

// equivalent using the runtime function directly
SEL sel = @selector(uppercaseString);
NSString *same = [name performSelector:sel];

Key point: Saying 'the runtime resolves the method at send time' is the phrase the key signal is. It sets up every later runtime question.

Q3. What happens when you send a message to nil?

Nothing happens, and the send returns a zero value: nil for object returns, 0 for numeric returns, and a zeroed struct for struct returns. There's no crash and no exception, unlike calling a method on null in many other languages. This is deliberate runtime behavior, and the returned zero is well defined.

This is deliberate and idiomatic. It lets you skip a lot of nil checks, but it also hides bugs when a method silently does nothing because its receiver was unexpectedly nil.

objc
NSString *missing = nil;
NSUInteger len = [missing length];   // 0, no crash
NSString *up = [missing uppercaseString];   // nil

if ([missing length] == 0) {
    // careful: nil and empty string both land here
}

Key point: The follow-up is 'is that a good thing?'. The honest answer, convenient but it can mask nil bugs, shows judgment.

Q4. What is the id type?

id is a generic pointer to any Objective-C object. A variable typed id can hold any instance, and the runtime figures out which method to call when you send it a message, so id is how Objective-C does dynamic typing.

You lose compile-time type checking with id, so use a concrete type like NSString * when you know it. id is right for containers and generic APIs where the exact class varies.

objc
id thing = @"hello";        // holds an NSString
thing = @42;                 // now holds an NSNumber
thing = @[@1, @2];           // now holds an NSArray

NSLog(@"%@", [thing class]);  // prints the real class at runtime

Q5. What is the difference between the @interface and @implementation of a class?

@interface declares the class: its superclass, properties, and method signatures. It's the public contract, traditionally in a .h header. @implementation contains the actual method bodies, traditionally in a .m file.

Splitting them lets other files import only the header and know how to use the class without seeing its internals. Private methods can live in the .m without appearing in the header.

objc
// Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (NSString *)greeting;
@end

// Person.m
@implementation Person
- (NSString *)greeting {
    return [NSString stringWithFormat:@"Hi, %@", self.name];
}
@end

Q6. What is the difference between NSString and NSMutableString?

NSString is immutable: once created, its characters can't change, and any method that looks like it edits the string actually returns a new one. NSMutableString is a subclass that can be modified in place with methods like appendString: and replaceCharactersInRange:, so you build it up step by step without allocating a fresh object each time.

Reach for NSString by default because immutability is safer to share; use NSMutableString when you're building a string in steps. The same pattern repeats across the Foundation collections.

objc
NSString *fixed = @"log:";
// [fixed appendString:@" ok"]; // no such method, NSString is immutable

NSMutableString *line = [NSMutableString stringWithString:@"log:"];
[line appendString:@" ok"];   // @"log: ok"

Key point: The immutable-by-default, mutable-subclass pattern (NSArray/NSMutableArray, NSDictionary/NSMutableDictionary) is worth naming. It shows you see the Foundation design, not just one class.

Q7. What are NSArray and NSDictionary?

NSArray is an ordered collection of objects; NSDictionary is an unordered collection of key-value pairs. Both are immutable, with NSMutableArray and NSMutableDictionary as the changeable versions. They store objects only, so numbers and booleans get wrapped in NSNumber.

Literal syntax makes them concise: @[a, b] for arrays and @{key: value} for dictionaries. Keys in an NSDictionary are copied and must conform to NSCopying.

objc
NSArray *teams = @[@"eng", @"ops"];
NSDictionary *scores = @{@"ada": @91, @"ben": @84};

NSNumber *first = teams[0] ? scores[@"ada"] : nil;   // @91
NSMutableArray *queue = [NSMutableArray array];
[queue addObject:@"task"];

Q8. What does @property do, and what are getters and setters?

@property declares an object's public attribute and tells the compiler to synthesize a getter, a setter, and a backing instance variable for it. Reading self.name calls the getter; assigning self.name = x calls the setter.

Attributes in parentheses control the details: nonatomic or atomic for thread safety, and strong, weak, copy, or assign for memory behavior. Dot syntax is just sugar for the method calls.

objc
@property (nonatomic, copy) NSString *title;

// these two lines are equivalent
self.title = @"Hi";       // calls -setTitle:
[self setTitle:@"Hi"];

NSString *t = self.title; // calls -title

Q9. What do the strong, weak, copy, and assign property attributes mean?

strong retains the object, keeping it alive while the property holds it. weak does not retain and is set to nil automatically when the target deallocates, which breaks retain cycles. copy stores an immutable snapshot. assign is a plain assignment with no memory management, used for primitives.

The everyday rules: strong for owned objects, weak for delegates and back-references, copy for NSString and block properties, assign for scalars like NSInteger and BOOL.

AttributeRetains?Typical use
strongYesObjects this instance owns
weakNo, nils outDelegates, parent back-references
copySnapshotsNSString, NSArray, block properties
assignNoPrimitives: NSInteger, BOOL, CGFloat

Key point: Knowing copy for NSString and weak for delegate is the fresher bar. The reason (mutable subclass, retain cycle) is what earns the tick.

Q10. What is BOOL in Objective-C, and how does it differ from bool?

BOOL is Objective-C's boolean type with values YES and NO. On most modern platforms it maps to the C99 bool, but historically it was a signed char, so it could hold values other than 0 and 1.

The practical advice: use YES and NO, and don't compare a BOOL directly to YES, because any nonzero value is truthy but only 1 equals YES. Test the value itself: if (flag), not if (flag == YES).

objc
BOOL isReady = YES;
if (isReady) { /* good */ }
// if (isReady == YES) risks trouble if isReady holds, say, 2

Q11. What is the difference between self and super?

self refers to the current object, so [self method] starts method lookup on the object's own class. super sends the same message but starts the lookup in the superclass, which is how an override calls the inherited behavior.

The classic use is in an init or a subclass override: call [super init] first, then add the subclass's own setup.

objc
- (instancetype)init {
    self = [super init];   // run NSObject's setup first
    if (self) {
        _count = 0;        // then this subclass's setup
    }
    return self;
}

Q12. What do alloc and init do, and why are they usually paired?

alloc allocates memory for a new instance and zeroes its instance variables; init sets up that instance's starting state and returns it. You pair them as [[Class alloc] init] because allocation without initialization gives you a half-built object.

Custom initializers follow the init prefix (initWithName:) and should call the superclass initializer first. Many classes also offer convenience constructors like [NSString stringWithFormat:].

objc
Person *p = [[Person alloc] init];
Person *q = [[Person alloc] initWithName:@"Ada"];

// convenience constructor pattern
NSArray *a = [NSArray arrayWithObjects:@1, @2, nil];

Q13. What is instancetype and why prefer it over id for initializers?

instancetype is a return type that means an instance of the receiver's class. In an initializer or factory method it tells the compiler the exact type coming back, so the result stays type-checked instead of degrading to the untyped id.

Return instancetype from init methods and class factory methods. The compiler then flags misuse of the returned object at compile time.

objc
// with instancetype the compiler knows the exact type
- (instancetype)initWithName:(NSString *)name;

// factory method
+ (instancetype)personWithName:(NSString *)name;

Q14. What is the difference between a class method and an instance method?

An instance method acts on a specific object and is declared with a minus sign: - (void)greet. A class method acts on the class itself and is declared with a plus sign: + (instancetype)new. You call class methods on the class, instance methods on an instance.

Class methods are common for factory constructors and utilities that don't need an instance, like [NSDate date].

objc
@interface Counter : NSObject
+ (instancetype)counter;   // class method (+)
- (void)increment;         // instance method (-)
@end

Counter *c = [Counter counter];
[c increment];

Q15. What is a selector (SEL)?

A selector is the name of a method as a lightweight runtime value of type SEL. @selector(doWork:) gives you the selector for a method, and you can pass it around and invoke it later with performSelector: or store it for delegation and target-action.

Selectors are just names, not implementations. Two unrelated classes can share a selector and each provides its own method for it, which is what makes dynamic dispatch flexible.

objc
SEL action = @selector(save:);
if ([target respondsToSelector:action]) {
    [target performSelector:action withObject:document];
}

// target-action, common in UIKit
[button addTarget:self action:@selector(tapped:)
 forControlEvents:UIControlEventTouchUpInside];

Q16. What is NSLog and what does the %@ format specifier do?

NSLog prints a formatted message to the console, like printf but for Objective-C. The %@ specifier prints any object by calling its description method, so %@ works for NSString, NSArray, your own classes, and more.

Other specifiers carry over from C: %d for int, %ld for long, %f for double. Mismatching a specifier and its argument is a frequent crash source.

objc
NSString *name = @"Ada";
NSInteger score = 91;
NSLog(@"%@ scored %ld", name, (long)score);
// Ada scored 91

Q17. How does inheritance work in Objective-C?

A class inherits from one superclass, named after the colon in @interface Child : Parent. The subclass gets the parent's properties and methods, can add its own, and can override inherited methods. Objective-C is single inheritance only; protocols cover the multiple-interface need.

NSObject sits at the root of almost every class and provides basics like isEqual:, description, and the memory-management methods.

objc
@interface Animal : NSObject
- (NSString *)sound;
@end

@interface Dog : Animal   // Dog inherits from Animal
@end

@implementation Dog
- (NSString *)sound { return @"woof"; }  // override
@end

Q18. What is the difference between isKindOfClass: and isMemberOfClass:?

isKindOfClass: returns YES if the object is an instance of that class or any subclass of it, so it respects the whole inheritance chain. isMemberOfClass: returns YES only for that exact class and treats subclasses as a no, which makes it far stricter and much less commonly the one you actually want in real code.

Use isKindOfClass: almost always, because it respects the class hierarchy. isMemberOfClass: is for the rare case where you need the exact type and want to exclude subclasses.

objc
NSMutableArray *m = [NSMutableArray array];
[m isKindOfClass:[NSArray class]];      // YES (subclass)
[m isMemberOfClass:[NSArray class]];    // NO (not exact)
[m isMemberOfClass:[NSMutableArray class]]; // YES

Q19. What is fast enumeration?

Fast enumeration is the for (Type *item in collection) loop for iterating arrays, sets, and dictionaries. It's concise and efficient because the collection hands items to the loop in batches under the hood.

The one rule: don't mutate the collection while enumerating it, or you'll trigger an exception. Collect changes and apply them after the loop, or iterate a copy.

objc
NSArray *names = @[@"ada", @"ben", @"cy"];
for (NSString *name in names) {
    NSLog(@"%@", [name uppercaseString]);
}

// dictionaries enumerate their keys
for (NSString *key in scores) {
    NSLog(@"%@ = %@", key, scores[key]);
}

Q20. What is the description method and when is it called?

description returns an NSString that represents the object, and it's what %@ and the debugger's po command print. NSObject's default gives you the class name and address, which isn't useful, so you override it to show meaningful state.

Override description for readable logs and debugging. debugDescription is a separate hook the debugger prefers if you want different output there.

objc
- (NSString *)description {
    return [NSString stringWithFormat:@"<Person %@ score=%ld>",
            self.name, (long)self.score];
}

NSLog(@"%@", person);   // uses your description
Back to question list

Objective-C Intermediate Interview Questions

Intermediate20 questions

For candidates with working experience: the runtime, memory management, and the patterns that separate users from understanders.

Q21. What is ARC and how does it work?

ARC, Automatic Reference Counting, is a compiler feature that inserts retain and release calls for you at compile time based on how you use objects. It isn't a garbage collector: there's no runtime scanning, just the same reference counting you'd write by hand, added automatically.

When an object's retain count drops to zero, it's deallocated immediately. You declare ownership with strong, weak, and unsafe_unretained, and ARC does the rest. Retain cycles still need your attention because ARC can't detect them.

objc
// under ARC you write this
Person *p = [[Person alloc] init];
// and the compiler effectively inserts [p release] when p goes out of scope

@property (nonatomic, strong) NSDate *created;  // retained
@property (nonatomic, weak) id<MyDelegate> delegate;  // not retained

Key point: The line that lands: 'ARC is compile-time reference counting, not garbage collection.' Interviewers ask this to separate people who understand the model from people who just trust it.

Watch a deeper explanation

Video: Objective-C Memory Management with ARC - raywenderlich.com (Kodeco, YouTube)

Q22. In manual reference counting, what do retain, release, and autorelease do?

retain increases an object's reference count by one; release decreases it by one; when the count hits zero the object is deallocated. autorelease marks an object to be released later, when the current autorelease pool drains, so you can return an object without leaking it.

The rule of thumb was: if you created an object with alloc, new, copy, or retain, you owned it and had to balance it with a release. ARC now writes these calls for you, but the model is what interviews probe.

objc
// manual reference counting (pre-ARC or MRC files)
Person *p = [[Person alloc] init];  // count 1, you own it
[p retain];                          // count 2
[p release];                         // count 1
[p release];                         // count 0 -> dealloc

Q23. What is a retain cycle and how do you break it?

A retain cycle happens when two objects hold strong references to each other, so neither's count ever reaches zero and both leak. The classic case is a parent holding a child strongly while the child holds the parent strongly, often through a delegate.

Break it by making one reference weak, usually the back-reference. Delegates are almost always weak for exactly this reason, and blocks that capture self use a weak-self pattern.

objc
// leaks: both sides strong
@property (nonatomic, strong) Node *parent;   // BAD as a back-ref

// fixed: back-reference is weak
@property (nonatomic, weak) Node *parent;     // GOOD
@property (nonatomic, strong) NSArray<Node *> *children;

Key point: Naming the delegate-as-weak convention and the weak-self block pattern in the same answer signals real production experience with memory.

Q24. What is the delegate pattern and how do you implement it?

Delegation lets one object hand off decisions or events to another. You define a protocol of callback methods, add a weak delegate property typed to that protocol, and call the delegate's methods at the right moments. UIKit uses it everywhere: table views, text fields, scroll views.

The delegate property is weak to avoid a retain cycle, and you guard optional methods with respondsToSelector: before calling them.

objc
@protocol DownloaderDelegate <NSObject>
- (void)downloaderDidFinish:(NSData *)data;
@end

@interface Downloader : NSObject
@property (nonatomic, weak) id<DownloaderDelegate> delegate;
@end

// inside Downloader when done:
if ([self.delegate respondsToSelector:@selector(downloaderDidFinish:)]) {
    [self.delegate downloaderDidFinish:result];
}

Key point: the question needs the weak delegate property AND the respondsToSelector: guard for optional methods. Missing either indicates textbook-only knowledge.

Watch a deeper explanation

Video: iOS Tutorial: Protocols and Delegates Explained Part 1 (Code Pro, YouTube)

Q25. How do required and optional protocol methods work?

A protocol declares methods a class can adopt. By default the methods are @required, so an adopting class must implement them or the compiler warns. Marking a section @optional means classes may skip those, which is why delegate methods are usually optional.

Because optional methods might be missing, you check respondsToSelector: before calling one. A class states adoption in angle brackets: MyClass : NSObject <SomeProtocol>.

objc
@protocol ListDelegate <NSObject>
@required
- (NSInteger)numberOfRows;
@optional
- (void)didSelectRow:(NSInteger)row;
@end

@interface Controller : NSObject <ListDelegate>
@end

Watch a deeper explanation

Video: Objective-C Protocol and Delegates- raywenderlich.com (Kodeco, YouTube)

Q26. What is a category and what are its limits?

A category adds methods to an existing class, even one you don't have the source for, without subclassing. It's how teams extend NSString or UIView with helper methods that read like they belong to the class.

The limits matter: a category can't add instance variables or stored properties, and if two categories define the same method the winner is undefined. For added state you use associated objects via the runtime.

objc
@interface NSString (Trimming)
- (NSString *)trimmed;
@end

@implementation NSString (Trimming)
- (NSString *)trimmed {
    return [self stringByTrimmingCharactersInSet:
            [NSCharacterSet whitespaceCharacterSet]];
}
@end

NSString *clean = [@"  hi  " trimmed];  // @"hi"

Q27. What is the difference between a category and a class extension?

A class extension is an anonymous category, @interface MyClass (), declared in the .m file. Unlike a normal category it can add instance variables and private properties, and the compiler requires its methods to be implemented in the main @implementation.

Use an extension for a class's private surface, properties and methods only that class should see. Use a category to add methods to classes you don't own or to split a large class across files.

objc
// in Downloader.m: a class extension for private state
@interface Downloader ()
@property (nonatomic, strong) NSURLSession *session;  // private
- (void)handleResponse:(NSData *)data;                // private
@end
CategoryClass extension
Add methodsYesYes
Add ivars/propertiesNoYes
Needs class sourceNoYes (same .m)
Typical useExtend any classPrivate surface of your own class

Q28. What are blocks and how do they capture variables?

A block is a chunk of code you can pass around like an object, Objective-C's version of a closure. It captures variables from its surrounding scope, so it's ideal for completion handlers, callbacks, and enumeration. The caret syntax ^{ ... } defines one.

By default a block captures a const copy of a local variable, so changing that local afterward doesn't affect the block. Mark a variable __block to let the block modify it and see later changes.

objc
int base = 10;
int (^addBase)(int) = ^(int x) { return x + base; };
addBase(5);   // 15

__block int total = 0;
void (^tally)(int) = ^(int n) { total += n; };  // __block lets it write
tally(3); tally(4);   // total == 7

Key point: The const-copy-by-default vs __block distinction is the follow-up. Getting it right, with why blocks capture self strongly, sets up the retain-cycle discussion.

Watch a deeper explanation

Video: Objective-C Blocks - raywenderlich.com (Kodeco, YouTube)

Q29. Why do blocks cause retain cycles, and what is the weakSelf pattern?

A block strongly captures any object it references, including self. If self also holds the block, for example as a stored completion handler, you get a retain cycle: self keeps the block, the block keeps self, neither deallocates.

The fix is to capture a weak reference. Create __weak typeof(self) weakSelf = self, use weakSelf inside the block, and often re-strongify it at the top of the block so it can't vanish mid-execution.

objc
__weak typeof(self) weakSelf = self;
self.completion = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (!strongSelf) return;
    [strongSelf refresh];
};

Breaking a block retain cycle

1Spot the cycle
self stores a block that references self
2Capture weakly
__weak typeof(self) weakSelf = self
3Use weakSelf
reference weakSelf inside the block, not self
4Re-strongify
__strong copy at the top so it survives the block body

The weak-then-strong dance is the production-grade version. weakSelf alone can be deallocated partway through a long block.

Q30. What is an autorelease pool and when do you add one manually?

An autorelease pool holds objects marked autorelease and sends them release when the pool drains. The main run loop wraps each iteration in a pool, so most code never touches pools directly.

You add an @autoreleasepool block inside a tight loop that creates many temporary objects, so they're freed each iteration instead of piling up until the loop ends. Long-running background threads also want their own pool.

objc
for (NSString *path in thousandsOfPaths) {
    @autoreleasepool {
        NSData *data = [NSData dataWithContentsOfFile:path];
        [self process:data];
    }   // temporaries freed each iteration, memory stays flat
}

Q31. Why declare NSString properties as copy instead of strong?

NSMutableString is a subclass of NSString, so a strong NSString property can be handed a mutable string. The caller could then mutate it behind your back and change your object's state unexpectedly. copy takes an immutable snapshot at assignment, so later mutations can't reach you.

The same reasoning applies to NSArray, NSDictionary, and NSSet properties. For a genuinely immutable value handed in, copy is nearly free because immutable objects return themselves.

objc
@property (nonatomic, copy) NSString *title;

NSMutableString *m = [NSMutableString stringWithString:@"Hi"];
obj.title = m;         // copy snapshots "Hi"
[m appendString:@"!"]; // obj.title stays "Hi", not "Hi!"

Q32. How do you make a class copyable with NSCopying?

Adopt the NSCopying protocol and implement copyWithZone:. Inside it, create a new instance and copy over the relevant properties. Then callers can send copy to your object and get an independent duplicate.

Decide between a shallow copy (copy the references) and a deep copy (copy the referenced objects too). NSCopying is also what lets your objects be dictionary keys, since keys are copied.

objc
@interface Point : NSObject <NSCopying>
@property (nonatomic) CGFloat x, y;
@end

@implementation Point
- (id)copyWithZone:(NSZone *)zone {
    Point *c = [[[self class] allocWithZone:zone] init];
    c.x = self.x; c.y = self.y;
    return c;
}
@end

Q33. What is the contract between isEqual: and hash?

If two objects are equal by isEqual:, they must return the same hash. Break that and the objects misbehave in NSSet and NSDictionary, showing up twice or failing lookups. The default isEqual: compares pointer identity, so override both together for value equality.

Base hash on the same immutable fields you compare in isEqual:. Don't include mutable fields, or an object's hash changes while it sits in a set.

objc
- (BOOL)isEqual:(id)other {
    if (self == other) return YES;
    if (![other isKindOfClass:[Point class]]) return NO;
    Point *p = other;
    return self.x == p.x && self.y == p.y;
}
- (NSUInteger)hash {
    return @(self.x).hash ^ @(self.y).hash;
}

Q34. When do you use NSException versus NSError?

NSException signals programmer errors: out-of-bounds access, unrecognized selectors, broken invariants. In Cocoa you generally don't catch these; you fix the bug. NSError represents expected, recoverable failures like a missing file or a network timeout, passed back by reference.

So the convention is: NSError for conditions you handle at runtime, NSException for bugs. Methods take an NSError ** out-parameter and return NO or nil on failure.

objc
NSError *error = nil;
NSString *text = [NSString stringWithContentsOfFile:path
                 encoding:NSUTF8StringEncoding error:&error];
if (!text) {
    NSLog(@"read failed: %@", error.localizedDescription);
}

Key point: The Cocoa convention (NSError for recoverable, NSException for bugs, and don't catch exceptions in normal flow) is the answer. Treating exceptions like Java's is the tell of someone new to Cocoa.

Q35. What is Grand Central Dispatch and how do you use it?

GCD is Apple's concurrency framework built on dispatch queues. You submit blocks to queues and the system manages threads for you. The main queue runs UI work on the main thread; global concurrent queues run background work; serial queues run one block at a time for safe state access.

The everyday pattern is do heavy work on a background queue, then hop back to the main queue to touch the UI.

objc
dispatch_async(dispatch_get_global_queue(
    DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSData *data = [self loadHeavyData];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateUIWith:data];   // UI on main thread
    });
});

Q36. Why must UI updates happen on the main thread?

UIKit and AppKit aren't thread-safe. Touching views, layers, or the run loop from a background thread causes undefined behavior: glitchy rendering, crashes, or state corruption. The main thread owns the UI and the main run loop.

So background work stays off the main thread to keep the interface responsive, and any UI change dispatches back to the main queue. Getting this wrong is one of the most common iOS bugs interviewers screen for.

Q37. What is the difference between atomic and nonatomic properties?

atomic, the default, makes the synthesized getter and setter thread-safe at the level of a single access, so you never read a half-written value. nonatomic drops that guarantee and is faster.

atomic protects one get or set, not a compound operation, so it isn't real thread safety for your logic. Most properties are declared nonatomic for speed, with genuine synchronization handled separately when needed.

Key point: The nuance the question needs: atomic guarantees a valid single read/write, not thread safety for your operations. People who think atomic makes their class thread-safe get caught here.

Q38. What is Key-Value Coding (KVC)?

KVC lets you read and write an object's properties by string name instead of direct accessors, using valueForKey: and setValue:forKey:. The runtime finds the matching getter, setter, or instance variable. It's the plumbing behind bindings, Core Data, and many generic APIs.

The cost is losing compile-time checks: a wrong key fails at runtime, not compile time. Use KVC for genuinely dynamic access, not as a substitute for normal property calls.

objc
[person setValue:@"Ada" forKey:@"name"];
NSString *n = [person valueForKey:@"name"];   // @"Ada"

// collection operator
NSNumber *total = [orders valueForKeyPath:@"@sum.amount"];

Q39. What is Key-Value Observing (KVO)?

KVO lets one object watch another's property and get notified when it changes. You register with addObserver:forKeyPath:options:context: and respond in observeValueForKeyPath:. It's built on KVC and works only for KVC-compliant properties.

The sharp edges: you must remove the observer before it deallocates or you crash, and untangling context pointers across a class hierarchy is fiddly. Many teams prefer notifications or block-based APIs for that reason.

Q40. How do you implement a thread-safe singleton?

Expose a class method that returns the one shared instance, and create it inside dispatch_once so it's built exactly once even under concurrent access. dispatch_once is the standard, race-free way to do lazy one-time setup.

Singletons fit genuinely shared services like a session or a cache. Overusing them creates hidden global state that's hard to test, so reach for them sparingly.

objc
+ (instancetype)shared {
    static Manager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
Back to question list

Objective-C Interview Questions for Experienced Developers

Experienced20 questions

advanced rounds probe the runtime, design judgment, and production scars. Expect every answer here to draw a follow-up.

Q41. What is the Objective-C runtime, and what does it do?

The Objective-C runtime is a C library that turns the language's dynamic features into machine behavior at runtime. Classes, objects, and methods are runtime data structures, and message sends become calls into the runtime that look up and invoke the right implementation.

It's what enables categories, associated objects, dynamic method resolution, introspection like class and respondsToSelector:, and swizzling. You can query and modify the class system while the program runs through functions like class_addMethod and objc_getClass.

Q42. What actually happens when you send a message? Explain objc_msgSend.

The compiler turns [obj doThing:x] into a call to objc_msgSend(obj, @selector(doThing:), x). That function takes the receiver and selector, finds the object's class, searches the method cache and then the class's method lists (walking up superclasses), and jumps to the matching implementation (an IMP).

If no method is found, the runtime gives you resolution hooks before it raises unrecognized selector. The method cache makes repeat sends to the same class fast.

objc
// these two are equivalent
NSString *up = [name uppercaseString];
NSString *up2 = ((NSString *(*)(id, SEL))objc_msgSend)(
                    name, @selector(uppercaseString));

How objc_msgSend resolves a message

1Take receiver and selector
objc_msgSend(obj, sel, args...)
2Check the method cache
fast path for recently used selectors on this class
3Search class and superclasses
walk method lists up the inheritance chain
4Invoke the IMP, or fall to resolution
call the found implementation, else dynamic resolution then forwarding

If every step fails, you get the classic 'unrecognized selector sent to instance' crash.

Q43. How does message forwarding work?

When an object gets a message it doesn't implement, the runtime gives three chances before crashing. First resolveInstanceMethod: can add a method dynamically. Then forwardingTargetForSelector: can hand the message to another object cheaply. Finally the full path builds an NSInvocation and passes it to forwardInvocation:, which needs methodSignatureForSelector: to describe the arguments.

This is how proxies, some mocking frameworks, and higher-order messaging work. It's also the last stop before unrecognized selector.

objc
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([self.backing respondsToSelector:aSelector]) {
        return self.backing;   // cheap redirect
    }
    return [super forwardingTargetForSelector:aSelector];
}

Key point: Naming the three stages in order (resolve, fast forward, full forwardInvocation:) is The production-ready answer. Most people only know it crashes.

Q44. What is method swizzling, and when is it justified?

Swizzling swaps two methods' implementations at runtime using the runtime API, usually in +load or a dispatch_once inside +initialize. It's how you inject behavior into a method you don't own, like logging every viewDidAppear.

The honest senior take: use it rarely. It's global, order-sensitive, and can break when Apple changes internals or two libraries swizzle the same method. Prefer subclassing, categories, or delegation, and reach for swizzling only when there's no seam.

objc
Method original = class_getInstanceMethod(cls, origSel);
Method swizzled = class_getInstanceMethod(cls, newSel);
method_exchangeImplementations(original, swizzled);
// now sending origSel runs the swizzled IMP and vice versa

Q45. What are associated objects and why do they exist?

Associated objects let you attach state to an instance at runtime with objc_setAssociatedObject and objc_getAssociatedObject, keyed by a static pointer. They exist to work around the fact that categories can't add instance variables, so a category can still store a value per instance.

You choose an association policy (retain, copy, assign) that mirrors property attributes. Use them sparingly: they're harder to see than declared properties and can surprise the next reader.

objc
static char kTagKey;

- (void)setTag:(NSString *)tag {
    objc_setAssociatedObject(self, &kTagKey, tag,
        OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)tag {
    return objc_getAssociatedObject(self, &kTagKey);
}

Q46. What is the difference between +load and +initialize?

+load runs once per class as the class is loaded into memory, very early, before main in most cases, and runs for every class and category that implements it. +initialize runs lazily the first time a class receives a message, and only if you send it one.

So +load is for the earliest possible setup like swizzling, but it's dangerous because ordering across classes isn't guaranteed and it blocks app launch. +initialize is safer for one-time class setup and should guard against running twice for subclasses.

objc
+ (void)initialize {
    if (self == [MyClass class]) {   // guard against subclass reruns
        // one-time setup
    }
}

Q47. What is a designated initializer, and why does the pattern matter?

The designated initializer is the one init method that does the full setup and calls the superclass's designated initializer. Other, convenience, initializers funnel through it rather than duplicating setup. The chain guarantees every object is fully initialized exactly once.

It matters for subclassing: a subclass overrides the designated initializer and calls super's, so no matter which convenience init a caller uses, the object ends up correctly built. Breaking the chain leaves objects half-initialized.

objc
// designated
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    if (self) { _name = [name copy]; _age = age; }
    return self;
}
// convenience funnels through the designated one
- (instancetype)initWithName:(NSString *)name {
    return [self initWithName:name age:0];
}

Q48. What is the difference between weak and unsafe_unretained?

Both are non-owning references, so neither keeps the target alive. The difference is cleanup: a weak reference is zeroed to nil automatically when the target deallocates, so accessing it afterward is safe. unsafe_unretained is not zeroed, so after deallocation it's a dangling pointer and using it crashes.

Use weak by default. unsafe_unretained exists for the rare case where the tiny overhead of zeroing weak matters, or for objects that don't support weak references, and it demands you manage lifetime by hand.

Q49. What are tagged pointers?

A tagged pointer packs a small object's value directly into the pointer bits instead of allocating an object on the heap. The runtime uses it for small NSNumber values, short NSString values, and NSDate, so common small objects cost no allocation and no reference counting.

The upshot for senior work: some pointers aren't real heap addresses, which is why you never rely on pointer equality for value objects and why isa isn't a plain class pointer anymore. It's mostly transparent, but it explains surprising behavior in low-level debugging.

Q50. What is the isa pointer, and what changed with the non-pointer isa?

Every Objective-C object starts with an isa pointer that told the runtime which class the object is, which is how message dispatch finds the method lists. On modern 64-bit runtimes isa is a non-pointer isa: a bitfield that packs the class plus reference count bits, weak-reference flags, and deallocation state alongside the class reference.

That packing is why retain counts for many objects live inline rather than in a side table, speeding up ARC. You rarely touch isa directly, and object_getClass is the supported way to read an object's class.

Q51. How do you build a thread-safe reader-writer cache with GCD?

Use a concurrent queue with dispatch_barrier for writes. Reads run concurrently with dispatch_sync (or dispatch_async) so many readers proceed at once. Writes use dispatch_barrier_async, which waits for in-flight reads to finish, runs alone, and blocks new work until it completes.

This gives the classic multiple-readers, single-writer guarantee without a lock on every read. It's a common senior design question for a shared in-memory cache.

objc
dispatch_queue_t q = dispatch_queue_create("cache",
    DISPATCH_QUEUE_CONCURRENT);

- (id)objectForKey:(NSString *)key {
    __block id value;
    dispatch_sync(q, ^{ value = self.store[key]; });   // concurrent read
    return value;
}
- (void)setObject:(id)obj forKey:(NSString *)key {
    dispatch_barrier_async(q, ^{ self.store[key] = obj; }); // exclusive write
}

Key point: The barrier-for-writes, concurrent-for-reads pattern is the answer they're fishing for. Reaching straight for @synchronized on every access indicates junior.

Q52. What are the options for locking, and how do they compare?

@synchronized(obj) is the simplest: it takes a recursive lock keyed on an object, but it's relatively slow. NSLock and NSRecursiveLock are explicit lock objects. os_unfair_lock is a fast, low-level lock that replaced the deprecated OSSpinLock. A serial dispatch queue often replaces locks entirely by serializing access.

The senior instinct is to avoid locks where a serial queue or immutable data removes the need, and to pick the lightest correct tool when a lock is genuinely required.

MechanismCostNotes
@synchronizedHigherConvenient, recursive, object-keyed
NSLockMediumExplicit lock/unlock, not recursive
os_unfair_lockLowFast, replaced OSSpinLock
Serial dispatch queueLowSerializes access, often no lock needed

Q53. What is toll-free bridging and how do __bridge casts work?

Some Foundation and Core Foundation types are interchangeable in memory, so NSString and CFStringRef can be cast between each other for free. That's toll-free bridging. Because ARC manages Objective-C objects but not CF objects, the cast must state who owns the retain.

__bridge casts without transferring ownership. __bridge_retained (or CFBridgingRetain) hands ownership to CF and you must CFRelease it. __bridge_transfer (or CFBridgingRelease) hands ownership back to ARC. Getting the direction wrong leaks or over-releases.

objc
NSString *s = @"hi";
CFStringRef cf = (__bridge CFStringRef)s;   // no ownership change

// give ARC ownership of a CF object it must release
NSString *owned = (__bridge_transfer NSString *)CFCopyDescription(x);

Q54. How do you serialize objects with NSCoding and NSSecureCoding?

NSCoding lets an object encode itself into and decode itself from an archive by implementing encodeWithCoder: and initWithCoder:. NSKeyedArchiver writes the archive. NSSecureCoding adds a check that the decoded object is the class you expect, closing a substitution attack in the older API.

Modern code should adopt NSSecureCoding and decode with decodeObjectOfClass:forKey: so a malicious archive can't smuggle in an unexpected class. This matters anywhere you unarchive untrusted data.

objc
+ (BOOL)supportsSecureCoding { return YES; }

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        _name = [coder decodeObjectOfClass:[NSString class]
                                    forKey:@"name"];
    }
    return self;
}

Q55. How does Objective-C interoperate with Swift?

In a mixed target, Swift sees Objective-C through an Objective-C bridging header, and Objective-C sees Swift through the generated ProductName-Swift.h header. Swift classes that inherit from NSObject or are marked @objc are visible to Objective-C; not everything Swift offers (generics, enums with associated values) bridges.

Practical seams: nullability annotations (nullable, nonnull) map to Swift optionals, and lightweight generics like NSArray<NSString *> import as typed arrays. Clean interop is mostly about annotating the Objective-C headers well.

Q56. You get EXC_BAD_ACCESS in an Objective-C app. How do you debug it?

EXC_BAD_ACCESS means you touched memory you shouldn't, usually a message to a deallocated object (a dangling pointer, classic in non-ARC or unsafe_unretained code). Turn on Zombies to catch messages to freed objects with a clear log, and run Address Sanitizer for broader memory errors.

Then narrow it: reproduce reliably, read the backtrace for the last valid frame, check ownership around the crashing object, and look for over-release, a raw C pointer outliving its object, or threading that frees an object another thread still uses.

Key point: Naming Zombies and Address Sanitizer plus a reproduce-narrow-verify loop scores higher than any single guess about the cause.

Q57. How do you find a memory leak or performance issue with Instruments?

Instruments is the profiler. The Leaks instrument flags objects with no remaining references to them; the Allocations instrument shows growth over time and abandoned memory (retained but unreachable in practice), which is where retain cycles show up. Time Profiler samples the call stack to find CPU hot spots.

The discipline the question needs: measure before changing anything. Reproduce the workload, capture a trace, find the growing allocation site or the hot stack, fix, and re-measure to confirm.

Q58. What are the three block storage types, and why do they matter?

A block starts life as a stack block, allocated on the stack. Sending it copy (or storing it in a strong property, which copies under ARC) moves it to the heap as a heap block so it can outlive the current scope. A block that captures nothing can be a global block with static storage.

This matters when you store a block for later: a stack block used after its scope returns is a crash. Under ARC, assigning to a strong block property copies it to the heap for you, which is why completion handlers survive.

Q59. Design question: how would you structure a networking layer in Objective-C?

Clarify needs first (auth, retries, caching, concurrency), then layer it: a request-building layer that turns typed requests into NSURLRequests, a transport layer over NSURLSession that returns results via completion blocks or a delegate, and a parsing layer that maps JSON to model objects with clear error handling through NSError.

Keep the transport off the main thread and marshal callbacks back to it, make model objects immutable, and hide NSURLSession behind a protocol so you can inject a fake session in tests. Close on the operational edges: timeouts, cancellation, and retry with backoff.

Key point: Structuring the answer (clarify, layers, testability, operational edges) is what's evaluated. A single class that does networking, parsing, and UI is the anti-pattern they're listening for.

Q60. How do you unit test Objective-C code?

XCTest is the built-in framework: subclass XCTestCase, write test methods prefixed with test, and assert with macros like XCTAssertEqual and XCTAssertNil. Asynchronous code uses XCTestExpectation to wait for a callback. You run tests from Xcode or xcodebuild in CI.

The design point interviewers probe is testability: inject dependencies (a session, a clock) behind protocols so you can pass fakes, and test behavior at the seams rather than reaching into internals.

objc
- (void)testDiscountApplies {
    Cart *cart = [[Cart alloc] initWithTotal:100];
    [cart applyDiscountPercent:10];
    XCTAssertEqualWithAccuracy(cart.total, 90.0, 0.001);
}
Back to question list

Objective-C vs Swift and C

Objective-C sits between raw C and modern Swift. Against C, it adds objects, a dynamic runtime, and the Foundation library while keeping full C compatibility, which is why it can call any C API directly. Against Swift, it trades compile-time safety and terse syntax for runtime flexibility: message passing, categories, and runtime introspection that Swift deliberately restricts. Teams keep Objective-C for legacy code, C and C++ interop, and cases where the dynamic runtime does real work, and reach for Swift on new code. Knowing where each fits, and saying it plainly, is itself an interview signal because it shows you pick tools on merits.

FeatureCObjective-CSwift
StyleProceduralOOP over CProtocol-oriented, multi-style
Method dispatchDirect callsDynamic messagingStatic by default, dynamic opt-in
Memory modelManual malloc/freeRetain counting, then ARCARC
Null safetyNonenil messaging is safe, no compile checksOptionals checked at compile time
C interopNativeDirect, same fileVia bridging headers

How to Prepare for a Objective-C Interview

Prepare in layers, and practice out loud. Most Objective-C rounds move from language and runtime questions to live coding to a memory-management or debugging discussion, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain them without notes, then read one tier up for the stretch questions.
  • Type and run every snippet in Xcode; modifying working code cements it far faster than reading.
  • Drill memory management until strong, weak, retain cycles, and autorelease pools are second nature, because it's the topic most interviews dig into.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical Objective-C interview flow

1Recruiter or phone screen
background, iOS experience, a few concept checks
2Language and runtime
messaging, categories, protocols, memory management
3Live coding
write and explain a class or method under observation
4Design or debugging
retain cycles, threading, reading unfamiliar Cocoa code

Earlier rounds increasingly run as AI coding interviews. The runtime and memory topics on this page are what gets probed at every stage.

Test Yourself: Objective-C Quiz

Ready to test your Objective-C knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which Objective-C topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Is Objective-C still worth learning in 2026?

Yes, for iOS and macOS roles. Swift is the default for new code, but millions of lines of Objective-C run in shipping apps, SDKs, and frameworks, and teams need people who can read, maintain, and bridge it. Many iOS interviews still ask Objective-C questions precisely because the legacy code isn't going away soon.

Do I need to know memory management by hand?

You should understand both. ARC handles retain and release automatically now, but the question expects you to explain what retain, release, and autorelease do, why retain cycles happen, and how strong and weak references break them. The manual model is what makes ARC make sense, so learn it even though you rarely write it.

Are these questions enough to pass an Objective-C interview?

They cover the question-answer portion well, but most iOS rounds also include live coding: writing a class or method under observation while explaining your thinking. solving small problems out loud with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

How long does it take to prepare for an Objective-C interview?

If you use Objective-C at work, one to two weeks of an hour a day covers this bank with practice runs. Coming from Swift or starting colder, plan three to four weeks and write code daily in Xcode, because reading answers without typing them is how preparation quietly fails.

Is there a way to test my Objective-C knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Objective-C questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 7 May 2026Last updated: 26 Jun 2026
Share: