Creational PatternsAbstract Factory PatternHard⏱️ ~4 min

Abstract Factory in Interviews: Questions and Machine Coding

Abstract Factory is a popular interview topic because it tests your understanding of object-oriented design principles, abstraction layers, and the ability to balance complexity with maintainability.

Common Interview Questions

Question 1: How does Abstract Factory differ from Factory Method?

Answer: Factory Method creates one product type through inheritance (subclasses decide which concrete class to instantiate), while Abstract Factory creates families of related products through composition (a factory object creates multiple related products). Factory Method uses one method, Abstract Factory uses multiple methods (one per product type). Use Factory Method when you have a single product hierarchy. Use Abstract Factory when you have multiple related products that must be created together consistently.

Question 2: How do you handle adding a new product type to all families?

Answer: Adding a new product type requires modifying the abstract factory interface and all concrete factory implementations, which violates the Open-Closed Principle for the factory dimension. This is an inherent trade-off of the pattern. To mitigate this, you can use default methods (in languages that support them) that throw UnsupportedOperationException, allowing factories to opt-in to new products. However, if you frequently add product types rather than product families, Abstract Factory may be the wrong pattern. Consider Builder or a more flexible creation mechanism instead.

Question 3: When should you use Abstract Factory versus Dependency Injection?

Answer: Modern frameworks with dependency injection (DI) containers handle object creation and wiring automatically, often eliminating the need for manual factory patterns. Use DI when your framework supports it and objects have complex dependency graphs. Use Abstract Factory when you need explicit control over family consistency, when working without a DI framework, or when building a library that consumers will extend. In practice, DI and Abstract Factory can coexist with the DI container injecting the appropriate factory based on configuration.

Interview Tip: If asked to implement Abstract Factory, immediately clarify the domain and number of product types. Confirm whether families must be consistent. This prevents over-engineering a solution that only needs Factory Method.

Machine Coding Scenario: Notification System

Design a notification system that sends messages through multiple channels (Email, SMS, Push). Each channel requires two components: a message formatter and a delivery service. Different notification profiles (Marketing, Transactional, Alert) format and deliver messages differently.

Step 1: Identify Product Families

Product Types:
First, MessageFormatter (formats content with styling and templates)
Second, DeliveryService (handles sending and retry logic)
Family Variants:
First, MarketingProfile (rich HTML formatting, batch delivery with unsubscribe links)
Second, TransactionalProfile (plain formatting, immediate high-priority delivery)
Third, AlertProfile (urgent formatting, aggressive retry with multiple fallback channels)

Step 2: Define Abstract Products

interface MessageFormatter {
format(content: String, metadata: Map): FormattedMessage
addHeader(header: String): void
addFooter(footer: String): void
}
interface DeliveryService {
send(message: FormattedMessage, recipient: String): DeliveryStatus
getRetryPolicy(): RetryPolicy
getDeliveryPriority(): Priority
}

Step 3: Create Abstract Factory

interface NotificationFactory {
createFormatter(channel: Channel): MessageFormatter
createDeliveryService(channel: Channel): DeliveryService
}
class MarketingNotificationFactory implements NotificationFactory {
createFormatter(channel: Channel): MessageFormatter {
if channel == EMAIL:
return new RichHTMLFormatter() // supports images, styling
else if channel == SMS:
return new ShortTextFormatter() // truncates, adds link
else:
return new PushBannerFormatter() // eye-catching format
}
createDeliveryService(channel: Channel): DeliveryService {
return new BatchDeliveryService() // groups messages, respects rate limits
}
}
class TransactionalNotificationFactory implements NotificationFactory {
createFormatter(channel: Channel): MessageFormatter {
return new PlainTextFormatter() // simple, clear, no distractions
}
createDeliveryService(channel: Channel): DeliveryService {
return new ImmediateDeliveryService() // sends instantly, high priority
}
}

Step 4: Implement Client

class NotificationService {
private factory: NotificationFactory
constructor(factory: NotificationFactory) {
this.factory = factory
}
sendNotification(content: String, recipient: String, channel: Channel) {
formatter = factory.createFormatter(channel)
deliveryService = factory.createDeliveryService(channel)
formattedMessage = formatter.format(content, metadata)
status = deliveryService.send(formattedMessage, recipient)
return status
}
}
// Usage:
marketingFactory = new MarketingNotificationFactory()
marketingService = new NotificationService(marketingFactory)
marketingService.sendNotification("Sale 50% off!", "[email protected]", EMAIL)
transactionalFactory = new TransactionalNotificationFactory()
transactionalService = new NotificationService(transactionalFactory)
transactionalService.sendNotification("Order confirmed", "[email protected]", EMAIL)

Key Design Decisions to Discuss

Why Abstract Factory is appropriate here: First, you have two product types (formatter and delivery service) that must work together consistently. Second, mixing a marketing formatter with transactional delivery would create inconsistent user experiences (flashy format but immediate priority doesn't match marketing intent). Third, adding new profiles (like SecurityAlert) requires only adding a new factory without modifying existing code.

Handling channel variations: The factory takes Channel as a parameter to createFormatter because formatting varies by channel even within the same profile. However, delivery service behavior is consistent across channels within a profile (marketing always batches, transactional always sends immediately), so it doesn't need the channel parameter. If this assumption changes later, you can add the parameter without breaking the interface contract.

Alternative considered: You could use Strategy pattern where NotificationService composes separate strategy objects for formatting and delivery. However, this requires the client to ensure strategies are compatible (not mixing marketing formatter with transactional delivery). Abstract Factory enforces this consistency automatically, which is why it's preferred when consistency is critical.

Common Mistake: Candidates often create one factory per channel (EmailFactory, SMSFactory) instead of one factory per profile. This misses the point of Abstract Factory, which is to create families of related objects. Channel is a parameter, not a family variant.

Follow-up Questions Interviewers Ask

How would you add a new channel like WhatsApp? Add WhatsApp to the Channel enum. Update each concrete factory's createFormatter method to handle the WhatsApp case. Delivery services likely don't change since they're channel-agnostic. This modification is localized to factory implementations and doesn't affect client code.

What if a new profile only needs custom formatting but uses standard delivery? Create the new factory (like CustomerServiceFactory) and implement createDeliveryService to return the same delivery service as transactional profile. Alternatively, use composition where factories can delegate to shared service creators for common components. However, this adds complexity, so evaluate whether the profile truly needs to be a separate family.

How do you test this design? First, unit test each concrete factory to verify it creates the correct product types. Second, use mock factories in NotificationService tests to verify it correctly uses formatter and delivery service. Third, integration tests verify that products from the same family work together correctly (marketing formatter produces output that marketing delivery service handles properly). Dependency injection makes testing easier by allowing test doubles to be injected.

Interview Tip: Always mention the Open-Closed Principle: the design is open for extension (new profiles) but closed for modification (existing client code doesn't change). This shows you understand SOLID principles, not just patterns.
💡 Key Takeaways
Abstract Factory differs from Factory Method by creating multiple related products instead of one product type
Adding new product types requires modifying all factories, which is a known limitation
Machine coding implementations should clearly identify product types and family variants
Factories ensure consistency by creating products designed to work together within the same family
Modern dependency injection can complement or replace manual factory implementation depending on framework capabilities
📌 Examples
1Notification system with formatter and delivery service across marketing, transactional, and alert profiles
2Document processing with parser and exporter for PDF, Word, and XML document families
3Game rendering with texture loader and shader compiler for high-quality, medium-quality, and low-quality graphics profiles
← Back to Abstract Factory Pattern Overview