Back to Questions
04

Design Patterns

Creational, structural, and behavioral patterns

Difficulty:
Session: 0 asked
1.

Singleton: implementation and thread-safety concerns

How do you implement a thread-safe Singleton? What are the concerns?

Mid

Singleton: Ensure a class has only one instance and provide global access.

Naive Implementation (Not Thread-Safe):

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {  // Race condition!
            instance = new Singleton();
        }
        return instance;
    }
}

Problem: Two threads can both see instance == null and create two instances.

Thread-Safe Implementations:

1. Synchronized Method (Simple but slow)

public static synchronized Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}

2. Double-Checked Locking

public class Singleton {
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Note: volatile is required to prevent instruction reordering.

3. Initialization-on-Demand Holder (Recommended)

public class Singleton {
    private Singleton() {}

    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

Thread-safe by JVM class loading mechanism, lazy initialization.

4. Enum Singleton (Best)

public enum Singleton {
    INSTANCE;

    public void doSomething() { }
}

Thread-safe, serialization-safe, reflection-proof.

Concerns:
1. Global state - Hard to test
2. Hidden dependencies - Not explicit
3. Violation of SRP - Creation + functionality
4. Difficult to mock - Use DI instead

Key Points to Look For:
- Knows thread-safety issues
- Can implement multiple approaches
- Understands when not to use

Follow-up: Why is Singleton often considered an anti-pattern?

2.

Factory Method vs Abstract Factory

What is the difference between Factory Method and Abstract Factory patterns?

Mid

Factory Method: Defines an interface for creating an object, but lets subclasses decide which class to instantiate.

// Factory Method
abstract class Dialog {
    abstract Button createButton();  // Factory method

    void render() {
        Button btn = createButton();
        btn.onClick(this::closeDialog);
        btn.render();
    }
}

class WindowsDialog extends Dialog {
    Button createButton() {
        return new WindowsButton();
    }
}

class MacDialog extends Dialog {
    Button createButton() {
        return new MacButton();
    }
}

Abstract Factory: Provides an interface for creating families of related objects.

// Abstract Factory
interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
    TextBox createTextBox();
}

class WindowsFactory implements GUIFactory {
    Button createButton() { return new WindowsButton(); }
    Checkbox createCheckbox() { return new WindowsCheckbox(); }
    TextBox createTextBox() { return new WindowsTextBox(); }
}

class MacFactory implements GUIFactory {
    Button createButton() { return new MacButton(); }
    Checkbox createCheckbox() { return new MacCheckbox(); }
    TextBox createTextBox() { return new MacTextBox(); }
}

// Client
class Application {
    private GUIFactory factory;

    void createUI() {
        Button btn = factory.createButton();
        Checkbox cb = factory.createCheckbox();
    }
}

Key Differences:

Aspect Factory Method Abstract Factory
Focus Single product Family of products
Implementation Method in class Separate factory class
Extension Subclass creator Implement factory interface
Use case One object type varies Multiple related objects

When to Use:

Factory Method:
- Single object creation varies by context
- Framework where client code extends base

Abstract Factory:
- Multiple related products
- Product families must be consistent
- Switching entire product lines

Key Points to Look For:
- Clear distinction between patterns
- Knows when to use each
- Can implement both

Follow-up: How do these patterns support the Open/Closed Principle?

3.

Builder pattern: when is it overkill?

When should you use the Builder pattern? When is it overkill?

Mid

Builder: Separate construction of complex objects from their representation.

When Builder Helps:

// Without Builder - confusing constructor
User user = new User("John", "Doe", "john@email.com", 30,
    "123 Street", "City", "12345", true, false, "premium");

// With Builder - readable
User user = User.builder()
    .firstName("John")
    .lastName("Doe")
    .email("john@email.com")
    .age(30)
    .address(Address.builder()
        .street("123 Street")
        .city("City")
        .zip("12345")
        .build())
    .active(true)
    .premium(true)
    .build();

Use Builder When:
1. Many parameters (4+ constructor arguments)
2. Optional parameters (many combinations)
3. Immutable objects with many fields
4. Complex construction (multi-step process)
5. Fluent interface desired for readability

Builder is Overkill When:
1. Few parameters (2-3 fields)

// Overkill for simple objects
Point point = Point.builder().x(10).y(20).build();

// Just use constructor
Point point = new Point(10, 20);
  1. All parameters required
// If everything is mandatory, constructor is clearer
User user = new User(name, email);  // Can't forget anything
  1. Mutable objects
// Just use setters
User user = new User();
user.setName("John");
user.setEmail("john@email.com");

Implementation:

public class User {
    private final String name;
    private final String email;
    private final int age;

    private User(Builder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private String name;
        private String email;
        private int age;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder email(String email) {
            this.email = email;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public User build() {
            // Validate required fields
            Objects.requireNonNull(name);
            Objects.requireNonNull(email);
            return new User(this);
        }
    }
}

Key Points to Look For:
- Knows when pattern is beneficial
- Recognizes overkill scenarios
- Can implement builder

Follow-up: How does Lombok's @Builder annotation work?

4.

Prototype: deep vs shallow copy

Explain the Prototype pattern. What's the difference between deep and shallow copy?

Mid

Prototype: Create new objects by cloning an existing prototype instance.

Use Case:
When object creation is expensive, or you need copies with slight modifications.

interface Prototype {
    Prototype clone();
}

class Document implements Prototype {
    private String content;
    private List<Page> pages;

    public Document clone() {
        Document copy = new Document();
        copy.content = this.content;
        copy.pages = new ArrayList<>(this.pages);  // Shallow!
        return copy;
    }
}

Shallow Copy:
Copies object references, not the objects themselves.

Document copy = original.clone();  // Shallow

copy.pages == original.pages;  // Same list reference!
copy.pages.add(newPage);  // Affects original too!
Original:        Copy:
+----------+    +----------+
| content  |    | content  |  (copied)
| pages ───────→ [Page1, Page2, Page3]
+----------+    +----------+

Deep Copy:
Recursively copies all objects.

class Document implements Prototype {
    public Document deepClone() {
        Document copy = new Document();
        copy.content = this.content;
        copy.pages = new ArrayList<>();
        for (Page page : this.pages) {
            copy.pages.add(page.deepClone());  // Clone each
        }
        return copy;
    }
}
Original:              Copy:
+----------+          +----------+
| content  |          | content  |
| pages ───┼──→ [P1, P2]   | pages ───┼──→ [P1', P2']
+----------+          +----------+

Java Cloning:

class Person implements Cloneable {
    private String name;
    private Address address;

    @Override
    protected Person clone() throws CloneNotSupportedException {
        Person copy = (Person) super.clone();  // Shallow
        copy.address = this.address.clone();   // Make it deep
        return copy;
    }
}

Copy Constructor Alternative:

class Person {
    public Person(Person other) {
        this.name = other.name;
        this.address = new Address(other.address);
    }
}

When to Use:
- Object creation is expensive
- Need many similar objects
- Runtime configuration of objects

Key Points to Look For:
- Understands shallow vs deep
- Knows cloning pitfalls
- Can implement both types

Follow-up: What problems can arise with Java's Cloneable interface?

5.

Object Pool pattern for expensive resources

What is the Object Pool pattern? When and how would you use it?

Senior

Object Pool: Reuse expensive objects instead of creating/destroying them repeatedly.

Use Cases:
- Database connections
- Thread pools
- Socket connections
- Large buffers

Implementation:

class ConnectionPool {
    private final List<Connection> available = new ArrayList<>();
    private final List<Connection> inUse = new ArrayList<>();
    private final int maxSize;
    private final ConnectionFactory factory;

    public synchronized Connection acquire() {
        if (available.isEmpty()) {
            if (inUse.size() < maxSize) {
                Connection conn = factory.create();
                inUse.add(conn);
                return conn;
            }
            // Wait or throw exception
            throw new PoolExhaustedException();
        }

        Connection conn = available.remove(available.size() - 1);
        inUse.add(conn);
        return conn;
    }

    public synchronized void release(Connection conn) {
        inUse.remove(conn);
        if (conn.isValid()) {
            available.add(conn);
        } else {
            conn.close();  // Don't return broken connections
        }
    }
}

Thread-Safe Implementation:

class ThreadSafePool<T> {
    private final BlockingQueue<T> pool;
    private final Supplier<T> factory;

    public ThreadSafePool(int size, Supplier<T> factory) {
        this.pool = new ArrayBlockingQueue<>(size);
        this.factory = factory;
        // Pre-populate
        for (int i = 0; i < size; i++) {
            pool.offer(factory.get());
        }
    }

    public T acquire() throws InterruptedException {
        return pool.take();  // Blocks if empty
    }

    public void release(T obj) {
        pool.offer(obj);
    }
}

With Try-With-Resources:

class PooledConnection implements AutoCloseable {
    private final Connection conn;
    private final ConnectionPool pool;

    @Override
    public void close() {
        pool.release(conn);  // Return to pool, don't close
    }
}

// Usage
try (PooledConnection conn = pool.acquire()) {
    // Use connection
}  // Automatically returned to pool

Considerations:
1. Validation - Check objects before reuse
2. Reset state - Clear object state on release
3. Sizing - Too small = contention, too large = waste
4. Timeout - Don't wait forever
5. Leaks - Track unreturned objects

Key Points to Look For:
- Knows when pooling helps
- Handles thread-safety
- Considers validation/reset

Follow-up: How does HikariCP implement connection pooling?

6.

Dependency Injection as a pattern

How is Dependency Injection a design pattern? What are the different types?

Mid

Dependency Injection: Technique where dependencies are provided ("injected") rather than created by the class itself.

Without DI:

class OrderService {
    private PaymentGateway gateway = new StripeGateway();  // Hardcoded!
    private EmailService email = new SmtpEmailService();   // Hardcoded!
}

With DI:

class OrderService {
    private final PaymentGateway gateway;
    private final EmailService email;

    // Dependencies injected
    OrderService(PaymentGateway gateway, EmailService email) {
        this.gateway = gateway;
        this.email = email;
    }
}

Types of Injection:

1. Constructor Injection (Preferred)

class Service {
    private final Repository repo;

    Service(Repository repo) {
        this.repo = repo;
    }
}
  • Dependencies explicit
  • Object always valid
  • Supports immutability

2. Setter Injection

class Service {
    private Repository repo;

    void setRepository(Repository repo) {
        this.repo = repo;
    }
}
  • Optional dependencies
  • Runtime reconfiguration
  • Can be in invalid state

3. Interface Injection

interface RepositoryAware {
    void setRepository(Repository repo);
}

class Service implements RepositoryAware {
    public void setRepository(Repository repo) { }
}
  • Rarely used
  • Framework-specific

4. Field Injection (Avoid)

class Service {
    @Inject
    private Repository repo;  // Reflection magic
}
  • Hard to test
  • Hidden dependencies
  • No immutability

Benefits:
1. Testability - Inject mocks
2. Flexibility - Swap implementations
3. Explicit dependencies - Clear requirements
4. Decoupling - Depends on interface

DI Containers:

// Spring
@Component
class OrderService {
    @Autowired
    public OrderService(PaymentGateway gateway) { }
}

// Guice
class AppModule extends AbstractModule {
    @Override
    void configure() {
        bind(PaymentGateway.class).to(StripeGateway.class);
    }
}

Key Points to Look For:
- Knows different injection types
- Prefers constructor injection
- Understands testing benefit

Follow-up: When would you use manual DI vs a DI framework?


Structural Patterns

7.

Adapter: class vs object adapter

What is the Adapter pattern? What's the difference between class and object adapters?

Mid

Adapter: Convert interface of a class into another interface clients expect.

Problem:

// Existing class with incompatible interface
class OldPaymentSystem {
    void makePayment(String account, int cents) { }
}

// Expected interface
interface PaymentProcessor {
    void pay(PaymentRequest request);
}

Object Adapter (Composition):

class PaymentAdapter implements PaymentProcessor {
    private OldPaymentSystem legacy;  // Composition

    PaymentAdapter(OldPaymentSystem legacy) {
        this.legacy = legacy;
    }

    @Override
    public void pay(PaymentRequest request) {
        legacy.makePayment(
            request.getAccountNumber(),
            (int) (request.getAmount() * 100)
        );
    }
}

Class Adapter (Inheritance):

class PaymentAdapter extends OldPaymentSystem implements PaymentProcessor {
    @Override
    public void pay(PaymentRequest request) {
        makePayment(  // Inherited method
            request.getAccountNumber(),
            (int) (request.getAmount() * 100)
        );
    }
}

Comparison:

Aspect Object Adapter Class Adapter
Mechanism Composition Inheritance
Flexibility Can adapt subclasses Single class only
Override behavior Cannot override Can override
Multiple adaptees Yes No (single inheritance)
Language support All OOP languages Needs multiple inheritance

When to Use:
- Object Adapter: More flexible, preferred in most cases
- Class Adapter: When you need to override adaptee behavior

Real Examples:
- InputStreamReader adapts InputStream to Reader
- Arrays.asList() adapts array to List interface
- JDBC drivers adapt database protocols

Key Points to Look For:
- Knows both approaches
- Prefers object adapter
- Can give real examples

Follow-up: How does Adapter differ from Facade?

8.

Decorator vs Inheritance for extending behavior

When would you use the Decorator pattern instead of inheritance to extend behavior?

Mid

Decorator: Attach additional responsibilities dynamically without subclassing.

Problem with Inheritance:

// Explosion of subclasses
class Coffee { }
class CoffeeWithMilk extends Coffee { }
class CoffeeWithSugar extends Coffee { }
class CoffeeWithMilkAndSugar extends Coffee { }
class CoffeeWithMilkAndSugarAndVanilla extends Coffee { }
// Combinatorial explosion!

Decorator Solution:

interface Coffee {
    double cost();
    String description();
}

class BasicCoffee implements Coffee {
    public double cost() { return 2.0; }
    public String description() { return "Coffee"; }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    CoffeeDecorator(Coffee coffee) { this.coffee = coffee; }
}

class MilkDecorator extends CoffeeDecorator {
    MilkDecorator(Coffee coffee) { super(coffee); }
    public double cost() { return coffee.cost() + 0.5; }
    public String description() { return coffee.description() + " + Milk"; }
}

class SugarDecorator extends CoffeeDecorator {
    SugarDecorator(Coffee coffee) { super(coffee); }
    public double cost() { return coffee.cost() + 0.2; }
    public String description() { return coffee.description() + " + Sugar"; }
}

// Usage - combine freely at runtime
Coffee order = new SugarDecorator(
    new MilkDecorator(
        new BasicCoffee()
    )
);
// "Coffee + Milk + Sugar" costs $2.70

When to Use Decorator:
1. Runtime composition - Combine behaviors dynamically
2. Multiple independent features - Avoid subclass explosion
3. Open/Closed Principle - Add features without modifying existing
4. Single Responsibility - Each decorator does one thing

When to Use Inheritance:
1. True IS-A relationship
2. Override/change behavior (not add)
3. Fixed hierarchy known at compile time
4. Access to protected members needed

Java I/O Example:

InputStream in = new BufferedInputStream(
    new FileInputStream("file.txt")
);
// FileInputStream → BufferedInputStream → InputStream

Key Points to Look For:
- Understands composition over inheritance
- Knows decorator enables runtime combination
- Can identify explosion problem

Follow-up: What's the difference between Decorator and Proxy?

9.

Facade: simplifying complex subsystems

What is the Facade pattern? Give an example of when you'd use it.

Junior

Facade: Provide a simplified interface to a complex subsystem.

Problem - Complex Subsystem:

// Client needs to know all these classes
VideoFile file = new VideoFile(filename);
CodecFactory factory = new CodecFactory();
Codec codec = factory.extract(file);
BitrateReader reader = new BitrateReader();
byte[] raw = reader.read(file, codec);
AudioMixer mixer = new AudioMixer();
byte[] audio = mixer.fix(raw);
// ... more complexity

Facade Solution:

class VideoConverter {
    public File convert(String filename, String format) {
        // Hides all complexity
        VideoFile file = new VideoFile(filename);
        Codec codec = CodecFactory.extract(file);
        byte[] raw = BitrateReader.read(file, codec);
        byte[] result = AudioMixer.fix(raw);
        return new File(result, format);
    }
}

// Simple client usage
VideoConverter converter = new VideoConverter();
File mp4 = converter.convert("movie.avi", "mp4");

Real Examples:

1. JDBC Facade

// Without facade - complex
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// Handle results, close resources...

// With facade (e.g., JdbcTemplate)
List<User> users = jdbcTemplate.query(sql, rowMapper);

2. SLF4J (Logging Facade)

// Hides Log4j, Logback, java.util.logging complexity
Logger logger = LoggerFactory.getLogger(MyClass.class);
logger.info("Simple logging");

Benefits:
1. Simplicity - Easy-to-use interface
2. Decoupling - Client doesn't depend on subsystem details
3. Layer organization - Clear entry point to subsystem
4. Testability - Mock the facade

Not a Facade:
- Facade doesn't prevent access to subsystem
- It's an additional interface, not a restriction

Key Points to Look For:
- Understands simplification purpose
- Knows it's about interface, not hiding
- Can give practical examples

Follow-up: How does Facade differ from Adapter?

10.

Proxy types: virtual, protection, remote

What are the different types of Proxy patterns and their use cases?

Mid

Proxy: Provide a surrogate for another object to control access.

1. Virtual Proxy (Lazy Loading)
Delays expensive object creation until needed.

interface Image {
    void display();
}

class RealImage implements Image {
    private byte[] data;

    RealImage(String filename) {
        loadFromDisk(filename);  // Expensive!
    }
}

class ImageProxy implements Image {
    private RealImage realImage;
    private String filename;

    ImageProxy(String filename) {
        this.filename = filename;  // No loading yet
    }

    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);  // Load on first use
        }
        realImage.display();
    }
}

2. Protection Proxy (Access Control)
Controls access based on permissions.

class DocumentProxy implements Document {
    private Document realDoc;
    private User user;

    public void edit(String content) {
        if (!user.hasPermission("EDIT")) {
            throw new AccessDeniedException();
        }
        realDoc.edit(content);
    }

    public String read() {
        if (!user.hasPermission("READ")) {
            throw new AccessDeniedException();
        }
        return realDoc.read();
    }
}

3. Remote Proxy (Network)
Represents object in different address space.

// Local proxy for remote service
class OrderServiceProxy implements OrderService {
    private String serviceUrl;

    public Order getOrder(Long id) {
        // Makes HTTP call to remote service
        return httpClient.get(serviceUrl + "/orders/" + id, Order.class);
    }
}

4. Caching Proxy
Caches results of expensive operations.

class CachingUserServiceProxy implements UserService {
    private UserService realService;
    private Map<Long, User> cache = new HashMap<>();

    public User findById(Long id) {
        return cache.computeIfAbsent(id, realService::findById);
    }
}

5. Logging/Monitoring Proxy

class LoggingProxy implements Service {
    private Service real;

    public void doWork() {
        log.info("Before doWork");
        long start = System.nanoTime();
        real.doWork();
        log.info("doWork took {}ms", (System.nanoTime() - start) / 1_000_000);
    }
}

Java Dynamic Proxy:

InvocationHandler handler = (proxy, method, args) -> {
    log.info("Calling " + method.getName());
    return method.invoke(realObject, args);
};

Service proxy = (Service) Proxy.newProxyInstance(
    Service.class.getClassLoader(),
    new Class[]{Service.class},
    handler
);

Key Points to Look For:
- Knows multiple proxy types
- Can give use case for each
- Understands dynamic proxies

Follow-up: How do Spring AOP proxies work?

11.

Composite pattern for tree structures

When would you use the Composite pattern? Explain with an example.

Mid

Composite: Compose objects into tree structures and treat individual objects and compositions uniformly.

Use Case: Part-whole hierarchies where clients should treat leaves and composites the same way.

// Component
interface FileSystemItem {
    String getName();
    long getSize();
    void print(String indent);
}

// Leaf
class File implements FileSystemItem {
    private String name;
    private long size;

    public String getName() { return name; }
    public long getSize() { return size; }
    public void print(String indent) {
        System.out.println(indent + name + " (" + size + " bytes)");
    }
}

// Composite
class Directory implements FileSystemItem {
    private String name;
    private List<FileSystemItem> children = new ArrayList<>();

    public void add(FileSystemItem item) { children.add(item); }
    public void remove(FileSystemItem item) { children.remove(item); }

    public String getName() { return name; }

    public long getSize() {
        return children.stream()
            .mapToLong(FileSystemItem::getSize)
            .sum();
    }

    public void print(String indent) {
        System.out.println(indent + name + "/");
        for (FileSystemItem child : children) {
            child.print(indent + "  ");
        }
    }
}

Usage:

Directory root = new Directory("root");
Directory docs = new Directory("docs");
docs.add(new File("readme.txt", 1024));
docs.add(new File("notes.txt", 512));
root.add(docs);
root.add(new File("config.json", 256));

root.print("");
// Output:
// root/
//   docs/
//     readme.txt (1024 bytes)
//     notes.txt (512 bytes)
//   config.json (256 bytes)

root.getSize();  // 1792 (sum of all files)

Real Examples:
- UI component hierarchies (Container has Components)
- Organization charts (Org has Departments has Employees)
- Menu systems (Menu has MenuItems and subMenus)
- Graphics (Group has Shapes and Groups)

When to Use:
- Part-whole hierarchies
- Clients should ignore difference between compositions and individuals
- Recursive structures

Key Points to Look For:
- Understands uniform treatment benefit
- Can implement component/leaf/composite
- Knows real-world examples

Follow-up: How would you implement an operation that only makes sense for composites?

12.

Bridge: separating abstraction from implementation

What problem does the Bridge pattern solve? Give an example.

Senior

Bridge: Decouple abstraction from implementation so both can vary independently.

Problem Without Bridge:

// Class explosion with multiple dimensions
class Shape { }
class RedCircle extends Shape { }
class BlueCircle extends Shape { }
class RedSquare extends Shape { }
class BlueSquare extends Shape { }
// Adding new shape = 2 new classes (each color)
// Adding new color = N new classes (each shape)

Bridge Solution:

// Implementation hierarchy
interface Color {
    void applyColor();
}

class RedColor implements Color {
    public void applyColor() { System.out.println("Red"); }
}

class BlueColor implements Color {
    public void applyColor() { System.out.println("Blue"); }
}

// Abstraction hierarchy
abstract class Shape {
    protected Color color;  // Bridge!

    Shape(Color color) { this.color = color; }
    abstract void draw();
}

class Circle extends Shape {
    Circle(Color color) { super(color); }
    void draw() {
        System.out.print("Circle in ");
        color.applyColor();
    }
}

class Square extends Shape {
    Square(Color color) { super(color); }
    void draw() {
        System.out.print("Square in ");
        color.applyColor();
    }
}

// Usage
Shape redCircle = new Circle(new RedColor());
Shape blueSquare = new Square(new BlueColor());

Two Independent Hierarchies:

Abstraction:         Implementation:
Shape                Color
├── Circle           ├── Red
├── Square           ├── Blue
└── Triangle         └── Green

Without Bridge: 3 × 3 = 9 classes
With Bridge: 3 + 3 = 6 classes

Real Example - Remote Controls:

// Abstraction
abstract class RemoteControl {
    protected Device device;

    void togglePower() {
        if (device.isEnabled()) device.disable();
        else device.enable();
    }
    abstract void channelUp();
}

// Refined Abstraction
class AdvancedRemote extends RemoteControl {
    void mute() { device.setVolume(0); }
}

// Implementation
interface Device {
    void enable();
    void disable();
    void setVolume(int volume);
}

class TV implements Device { }
class Radio implements Device { }

// Any remote works with any device
RemoteControl remote = new AdvancedRemote(new TV());

When to Use:
- Multiple dimensions of variation
- Avoid cartesian product of classes
- Need runtime binding of implementation
- Share implementation among abstractions

Key Points to Look For:
- Understands two-hierarchy concept
- Can explain class explosion problem
- Knows when pattern applies

Follow-up: How does Bridge differ from Adapter?

13.

Flyweight for memory optimization

What is the Flyweight pattern? When would you use it?

Senior

Flyweight: Share common state among many objects to reduce memory usage.

Intrinsic vs Extrinsic State:
- Intrinsic: Shared, immutable, context-independent
- Extrinsic: Unique, passed by client, context-dependent

Example: Text Editor

// Without Flyweight - each character is an object
class Character {
    char value;
    String font;      // Repeated!
    int fontSize;     // Repeated!
    Color color;      // Repeated!
    int positionX;
    int positionY;
}
// "Hello" = 5 objects with mostly repeated data

// With Flyweight
class CharacterStyle {  // Flyweight (shared)
    final String font;       // Intrinsic
    final int fontSize;      // Intrinsic
    final Color color;       // Intrinsic
}

class CharacterContext {  // Extrinsic state
    char value;
    int positionX;
    int positionY;
    CharacterStyle style;  // Reference to shared flyweight
}

class StyleFactory {
    private Map<String, CharacterStyle> styles = new HashMap<>();

    CharacterStyle getStyle(String font, int size, Color color) {
        String key = font + size + color;
        return styles.computeIfAbsent(key,
            k -> new CharacterStyle(font, size, color));
    }
}

Game Example - Trees in Forest:

// Flyweight - shared tree type data
class TreeType {
    final String name;
    final Color color;
    final Texture texture;  // Large image data

    void draw(int x, int y) {
        // Draw texture at position
    }
}

// Context - unique per tree
class Tree {
    int x, y;
    TreeType type;  // Shared flyweight

    void draw() { type.draw(x, y); }
}

class Forest {
    List<Tree> trees;
    TreeTypeFactory factory;

    void plantTree(int x, int y, String name) {
        TreeType type = factory.getTreeType(name);  // Reuse!
        trees.add(new Tree(x, y, type));
    }
}

Memory Comparison:

1000 trees without Flyweight:
- 1000 × (coords + name + color + 10MB texture) ≈ 10GB

1000 trees with Flyweight:
- 3 TreeTypes × 10MB = 30MB (shared)
- 1000 × (coords + reference) ≈ 20KB
- Total ≈ 30MB

When to Use:
- Large number of similar objects
- Objects share significant state
- Extrinsic state can be computed/passed
- Object identity doesn't matter

Key Points to Look For:
- Understands intrinsic vs extrinsic
- Can identify shareable state
- Knows memory benefit

Follow-up: How does Java's String pool use the Flyweight pattern?


Behavioral Patterns

14.

Observer pattern and event-driven design

Explain the Observer pattern. How does it enable event-driven design?

Mid

Observer: Define a one-to-many dependency so when one object changes state, all dependents are notified.

Implementation:

// Subject (Observable)
interface Subject {
    void attach(Observer o);
    void detach(Observer o);
    void notifyObservers();
}

// Observer
interface Observer {
    void update(Event event);
}

// Concrete Subject
class Stock implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private double price;

    public void setPrice(double price) {
        this.price = price;
        notifyObservers();
    }

    public void attach(Observer o) { observers.add(o); }
    public void detach(Observer o) { observers.remove(o); }

    public void notifyObservers() {
        Event event = new PriceChangeEvent(this, price);
        observers.forEach(o -> o.update(event));
    }
}

// Concrete Observers
class TradingBot implements Observer {
    public void update(Event event) {
        if (event instanceof PriceChangeEvent) {
            double price = ((PriceChangeEvent) event).getPrice();
            if (price < threshold) buy();
        }
    }
}

class AlertSystem implements Observer {
    public void update(Event event) {
        sendNotification(event.toString());
    }
}

Usage:

Stock apple = new Stock("AAPL");
apple.attach(new TradingBot());
apple.attach(new AlertSystem());
apple.attach(new PriceChart());

apple.setPrice(150.0);  // All observers notified

Event-Driven Benefits:
1. Loose coupling - Subject doesn't know observer types
2. Dynamic subscription - Add/remove at runtime
3. Broadcast - Multiple receivers for one event
4. Extensibility - Add observers without changing subject

Modern Java:

// PropertyChangeSupport
class Stock {
    private PropertyChangeSupport pcs = new PropertyChangeSupport(this);

    public void addListener(PropertyChangeListener l) {
        pcs.addPropertyChangeListener(l);
    }

    public void setPrice(double price) {
        double old = this.price;
        this.price = price;
        pcs.firePropertyChange("price", old, price);
    }
}

Common Uses:
- GUI event handling (button clicks)
- MVC pattern (model notifies views)
- Pub/sub messaging
- Real-time data feeds

Key Points to Look For:
- Knows push vs pull notification
- Understands decoupling benefit
- Can implement basic observer

Follow-up: What's the difference between Observer and Pub/Sub?

15.

Strategy vs State pattern differences

What's the difference between Strategy and State patterns? They look similar.

Mid

Both encapsulate behavior in separate classes, but for different purposes:

Strategy: Choose algorithm at runtime. Client selects strategy.
State: Change behavior when internal state changes. Object manages transitions.

Strategy Example:

interface PaymentStrategy {
    void pay(double amount);
}

class CreditCardPayment implements PaymentStrategy {
    void pay(double amount) { /* charge card */ }
}

class PayPalPayment implements PaymentStrategy {
    void pay(double amount) { /* use PayPal API */ }
}

class ShoppingCart {
    private PaymentStrategy strategy;

    void setPaymentStrategy(PaymentStrategy s) {
        this.strategy = s;  // Client chooses
    }

    void checkout(double total) {
        strategy.pay(total);
    }
}

// Usage - client explicitly chooses
cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(100);

State Example:

interface OrderState {
    void next(Order order);
    void cancel(Order order);
}

class PendingState implements OrderState {
    void next(Order order) {
        order.setState(new ProcessingState());  // Auto-transition
    }
    void cancel(Order order) {
        order.setState(new CancelledState());
    }
}

class ProcessingState implements OrderState {
    void next(Order order) {
        order.setState(new ShippedState());
    }
    void cancel(Order order) {
        throw new IllegalStateException("Cannot cancel processing order");
    }
}

class Order {
    private OrderState state = new PendingState();

    void setState(OrderState state) { this.state = state; }
    void proceed() { state.next(this); }  // State changes itself
}

// Usage - transitions happen automatically
order.proceed();  // Pending → Processing
order.proceed();  // Processing → Shipped

Key Differences:

Aspect Strategy State
Who changes Client explicitly Object/State internally
Awareness Strategies don't know each other States know about transitions
Purpose Select algorithm Model state machine
Transitions N/A Defined by states
Typical use Payment methods, sorting Order status, UI modes

When to Use:

Strategy:
- Multiple algorithms for same task
- Client should choose
- Algorithms are interchangeable

State:
- Object behavior depends on state
- State transitions are well-defined
- Replacing long if/switch on state

Key Points to Look For:
- Knows semantic difference
- Identifies who manages changes
- Can give examples for each

Follow-up: How would you implement an undo/redo feature using these patterns?

16.

Command pattern: undo/redo implementation

How does the Command pattern enable undo/redo functionality?

Mid

Command: Encapsulate a request as an object, allowing parameterization, queuing, and undo.

Basic Structure:

interface Command {
    void execute();
    void undo();
}

Text Editor Example:

class InsertTextCommand implements Command {
    private Document doc;
    private String text;
    private int position;

    InsertTextCommand(Document doc, int position, String text) {
        this.doc = doc;
        this.position = position;
        this.text = text;
    }

    public void execute() {
        doc.insert(position, text);
    }

    public void undo() {
        doc.delete(position, text.length());
    }
}

class DeleteTextCommand implements Command {
    private Document doc;
    private String deletedText;  // Store for undo
    private int position;

    public void execute() {
        deletedText = doc.getText(position, length);  // Save
        doc.delete(position, length);
    }

    public void undo() {
        doc.insert(position, deletedText);  // Restore
    }
}

Command History Manager:

class CommandHistory {
    private Stack<Command> undoStack = new Stack<>();
    private Stack<Command> redoStack = new Stack<>();

    void executeCommand(Command cmd) {
        cmd.execute();
        undoStack.push(cmd);
        redoStack.clear();  // Clear redo on new action
    }

    void undo() {
        if (undoStack.isEmpty()) return;
        Command cmd = undoStack.pop();
        cmd.undo();
        redoStack.push(cmd);
    }

    void redo() {
        if (redoStack.isEmpty()) return;
        Command cmd = redoStack.pop();
        cmd.execute();
        undoStack.push(cmd);
    }
}

Composite Command (Macro):

class MacroCommand implements Command {
    private List<Command> commands = new ArrayList<>();

    void add(Command cmd) { commands.add(cmd); }

    public void execute() {
        commands.forEach(Command::execute);
    }

    public void undo() {
        // Undo in reverse order
        for (int i = commands.size() - 1; i >= 0; i--) {
            commands.get(i).undo();
        }
    }
}

Benefits:
1. Undo/Redo - Commands remember how to reverse
2. Logging - Persist commands for audit
3. Queuing - Execute later, batch processing
4. Transactional - Rollback on failure

Key Points to Look For:
- Stores state for undo
- Manages command history
- Handles redo correctly

Follow-up: How would you implement persistent undo (survives restart)?

17.

Template Method vs Strategy

When would you use Template Method instead of Strategy?

Mid

Both allow varying parts of an algorithm, but differ in how:

Template Method: Inheritance-based. Algorithm skeleton in base class, steps overridden in subclasses.

abstract class DataProcessor {
    // Template method - final, can't override
    public final void process() {
        readData();
        processData();  // Hook - subclass implements
        writeData();
    }

    protected abstract void processData();  // Subclass varies this

    private void readData() { /* fixed step */ }
    private void writeData() { /* fixed step */ }
}

class CSVProcessor extends DataProcessor {
    @Override
    protected void processData() {
        // CSV-specific processing
    }
}

class XMLProcessor extends DataProcessor {
    @Override
    protected void processData() {
        // XML-specific processing
    }
}

Strategy: Composition-based. Entire algorithm encapsulated and injected.

interface ProcessingStrategy {
    void process(Data data);
}

class DataProcessor {
    private ProcessingStrategy strategy;

    DataProcessor(ProcessingStrategy strategy) {
        this.strategy = strategy;
    }

    public void process() {
        Data data = readData();
        strategy.process(data);  // Delegated to strategy
        writeData(data);
    }
}

class CSVStrategy implements ProcessingStrategy { }
class XMLStrategy implements ProcessingStrategy { }

// Can swap at runtime
processor.setStrategy(new XMLStrategy());

Comparison:

Aspect Template Method Strategy
Mechanism Inheritance Composition
Algorithm control Base class Strategy object
Change timing Compile-time Runtime
Steps overridden Parts (hooks) Entire algorithm
Class count Subclass per variant Strategy per variant

Use Template Method When:
- Algorithm structure is fixed
- Only certain steps vary
- Want to enforce algorithm skeleton
- Framework hooks (e.g., Servlet lifecycle)

Use Strategy When:
- Need runtime switching
- Entire algorithm varies
- Want to avoid inheritance
- Multiple strategies in same object

Key Points to Look For:
- Knows inheritance vs composition difference
- Understands when each is appropriate
- Can implement both

Follow-up: How does the Hollywood Principle relate to Template Method?

18.

Iterator: external vs internal iteration

What's the difference between external and internal iteration?

Junior

Iterator: Provide a way to access elements sequentially without exposing underlying representation.

External Iteration:
Client controls iteration explicitly.

// External - client controls loop
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
    String name = iterator.next();
    System.out.println(name);
}

// Or enhanced for-loop (still external)
for (String name : names) {
    System.out.println(name);
}

Internal Iteration:
Collection controls iteration, client provides action.

// Internal - collection controls loop
names.forEach(name -> System.out.println(name));

// With streams
names.stream()
    .filter(name -> name.length() > 3)
    .forEach(System.out::println);

Comparison:

Aspect External Internal
Control Client has loop Collection has loop
Flexibility More control Less control
Parallelism Manual Easy (parallelStream)
Code style Imperative Declarative
Early exit Easy (break) Harder

Custom Iterator:

class TreeIterator<T> implements Iterator<T> {
    private Stack<Node<T>> stack = new Stack<>();

    TreeIterator(Node<T> root) {
        pushLeft(root);
    }

    private void pushLeft(Node<T> node) {
        while (node != null) {
            stack.push(node);
            node = node.left;
        }
    }

    public boolean hasNext() {
        return !stack.isEmpty();
    }

    public T next() {
        Node<T> node = stack.pop();
        pushLeft(node.right);
        return node.value;
    }
}

// Usage
for (Integer value : tree) {  // Uses iterator
    System.out.println(value);
}

When to Use:

External:
- Need fine-grained control
- Early termination (break)
- Multiple iterators simultaneously
- Modify during iteration

Internal:
- Cleaner, declarative code
- Potential parallelism
- No early exit needed

Key Points to Look For:
- Knows both styles
- Understands trade-offs
- Can implement custom iterator

Follow-up: How do fail-fast iterators work?

19.

Chain of Responsibility: request handling

Explain Chain of Responsibility pattern with a practical example.

Mid

Chain of Responsibility: Pass request along a chain of handlers. Each handler decides to process or pass to next.

Example: HTTP Request Processing

abstract class Handler {
    private Handler next;

    void setNext(Handler next) {
        this.next = next;
    }

    void handle(Request request) {
        if (canHandle(request)) {
            process(request);
        } else if (next != null) {
            next.handle(request);
        }
    }

    abstract boolean canHandle(Request request);
    abstract void process(Request request);
}

class AuthenticationHandler extends Handler {
    boolean canHandle(Request req) { return true; }  // Always runs

    void process(Request req) {
        if (!isAuthenticated(req)) {
            req.setResponse(401, "Unauthorized");
            return;  // Stop chain
        }
        // Pass to next
        if (next != null) next.handle(req);
    }
}

class AuthorizationHandler extends Handler {
    void process(Request req) {
        if (!hasPermission(req)) {
            req.setResponse(403, "Forbidden");
            return;
        }
        if (next != null) next.handle(req);
    }
}

class ValidationHandler extends Handler {
    void process(Request req) {
        if (!isValid(req.getBody())) {
            req.setResponse(400, "Bad Request");
            return;
        }
        if (next != null) next.handle(req);
    }
}

class BusinessLogicHandler extends Handler {
    void process(Request req) {
        // Actually handle the request
        req.setResponse(200, "OK");
    }
}

Usage:

Handler chain = new AuthenticationHandler();
chain.setNext(new AuthorizationHandler())
     .setNext(new ValidationHandler())
     .setNext(new BusinessLogicHandler());

chain.handle(request);

Real Examples:
- Servlet filters
- Middleware in Express.js
- Exception handling (catch blocks)
- Event bubbling in UI
- Logging levels

Variants:
1. Stop on handle - First matching handler processes
2. All handlers run - Every handler gets chance
3. Transform and pass - Each handler modifies request

Benefits:
- Decouple sender from receivers
- Dynamic chain configuration
- Single responsibility per handler

Key Points to Look For:
- Understands chain traversal
- Knows stopping vs continuing
- Can give real examples

Follow-up: How do middleware chains in web frameworks use this pattern?

20.

Mediator: reducing coupling between objects

What problem does the Mediator pattern solve? Give an example.

Senior

Mediator: Define an object that encapsulates how a set of objects interact, promoting loose coupling.

Problem Without Mediator:

// Direct coupling - every component knows others
class Button {
    private TextBox textBox;
    private Label label;
    private Checkbox checkbox;

    void click() {
        textBox.clear();
        label.setText("Clicked");
        checkbox.uncheck();
    }
}
// Adding new component requires changing all others

With Mediator:

interface DialogMediator {
    void notify(Component sender, String event);
}

class RegistrationDialog implements DialogMediator {
    private Button submitBtn;
    private TextBox nameField;
    private TextBox emailField;
    private Checkbox termsBox;

    public void notify(Component sender, String event) {
        if (sender == termsBox && event.equals("check")) {
            submitBtn.setEnabled(termsBox.isChecked());
        }
        if (sender == submitBtn && event.equals("click")) {
            if (validateForm()) {
                submitForm();
            }
        }
        if (sender == nameField && event.equals("change")) {
            validateName();
        }
    }
}

// Components only know mediator
abstract class Component {
    protected DialogMediator mediator;

    void setMediator(DialogMediator m) { mediator = m; }
}

class Button extends Component {
    void click() {
        mediator.notify(this, "click");
    }
}

class Checkbox extends Component {
    void check() {
        mediator.notify(this, "check");
    }
}

Without vs With Mediator:

Without:          With:
A ←→ B            A → M ← B
↕   ↕             ↑     ↑
C ←→ D            C → M ← D

Connections: 6    Connections: 4

Real Examples:
- Air traffic control (planes communicate via tower)
- Chat room (users communicate via room)
- GUI forms (components via form controller)
- Event bus / Message broker

Trade-offs:
- Pro: Reduces coupling between components
- Pro: Centralizes complex logic
- Con: Mediator can become God Object
- Con: Single point of failure

Mediator vs Observer:
- Observer: One-to-many notification
- Mediator: Many-to-many coordination

Key Points to Look For:
- Understands coupling reduction
- Can identify God Object risk
- Knows when pattern applies

Follow-up: How do you prevent the Mediator from becoming a God Class?

21.

Memento for state snapshots

How does the Memento pattern work? When would you use it?

Senior

Memento: Capture and externalize an object's internal state so it can be restored later, without violating encapsulation.

Components:
- Originator: Object whose state we save
- Memento: Stores originator's state
- Caretaker: Manages mementos (undo history)

Implementation:

// Memento - stores state
class EditorMemento {
    private final String content;
    private final int cursorPosition;

    EditorMemento(String content, int cursor) {
        this.content = content;
        this.cursorPosition = cursor;
    }

    String getContent() { return content; }
    int getCursor() { return cursorPosition; }
}

// Originator - creates and restores from memento
class TextEditor {
    private String content = "";
    private int cursorPosition = 0;

    void type(String text) {
        content = content.substring(0, cursorPosition) +
                  text +
                  content.substring(cursorPosition);
        cursorPosition += text.length();
    }

    EditorMemento save() {
        return new EditorMemento(content, cursorPosition);
    }

    void restore(EditorMemento memento) {
        content = memento.getContent();
        cursorPosition = memento.getCursor();
    }
}

// Caretaker - manages history
class History {
    private Stack<EditorMemento> states = new Stack<>();

    void push(EditorMemento memento) {
        states.push(memento);
    }

    EditorMemento pop() {
        return states.pop();
    }
}

Usage:

TextEditor editor = new TextEditor();
History history = new History();

editor.type("Hello");
history.push(editor.save());  // Save state

editor.type(" World");
history.push(editor.save());  // Save state

editor.type("!!!");
// content = "Hello World!!!"

editor.restore(history.pop());
// content = "Hello World"

editor.restore(history.pop());
// content = "Hello"

Encapsulation:

// Nested class maintains encapsulation
class TextEditor {
    private String content;

    class Memento {  // Only Editor can access internals
        private final String savedContent;
        private Memento(String content) { this.savedContent = content; }
    }

    Memento save() { return new Memento(content); }
    void restore(Memento m) { content = m.savedContent; }
}

Use Cases:
- Undo/Redo functionality
- Checkpoint/rollback in transactions
- Game save states
- Versioning systems

Key Points to Look For:
- Understands encapsulation preservation
- Knows three participants
- Can implement basic version

Follow-up: How would you implement incremental mementos for large objects?

22.

Visitor pattern trade-offs

What are the trade-offs of the Visitor pattern? When is it appropriate?

Senior

Visitor: Represent an operation to be performed on elements of an object structure. Add new operations without changing element classes.

Implementation:

// Element interface
interface DocumentElement {
    void accept(Visitor visitor);
}

class Heading implements DocumentElement {
    String text;
    int level;
    void accept(Visitor v) { v.visit(this); }
}

class Paragraph implements DocumentElement {
    String content;
    void accept(Visitor v) { v.visit(this); }
}

class Image implements DocumentElement {
    String url;
    void accept(Visitor v) { v.visit(this); }
}

// Visitor interface
interface Visitor {
    void visit(Heading heading);
    void visit(Paragraph paragraph);
    void visit(Image image);
}

// Concrete visitors - new operations
class HTMLExportVisitor implements Visitor {
    void visit(Heading h) {
        output("<h" + h.level + ">" + h.text + "</h" + h.level + ">");
    }
    void visit(Paragraph p) { output("<p>" + p.content + "</p>"); }
    void visit(Image i) { output("<img src='" + i.url + "'/>"); }
}

class WordCountVisitor implements Visitor {
    private int count = 0;
    void visit(Heading h) { count += countWords(h.text); }
    void visit(Paragraph p) { count += countWords(p.content); }
    void visit(Image i) { /* images have no words */ }
}

Usage:

List<DocumentElement> document = getDocument();
Visitor htmlExporter = new HTMLExportVisitor();

for (DocumentElement element : document) {
    element.accept(htmlExporter);
}

Trade-offs:

Pro Con
Easy to add new operations Hard to add new element types
Related behavior in one class Breaks encapsulation (public methods)
Accumulate state across visits Double dispatch complexity
External to elements Requires element modification

When to Use:
- Object structure rarely changes
- Many distinct operations needed
- Operations don't fit in element classes
- Traversal logic should be external

When to Avoid:
- Element types change frequently (each new type = change all visitors)
- Only 1-2 operations needed
- Simple hierarchy
- Elements can handle operations themselves

Double Dispatch:

// 1. element.accept(visitor)  - dispatch on element type
// 2. visitor.visit(this)      - dispatch on visitor type

Key Points to Look For:
- Understands adding operations vs types trade-off
- Knows double dispatch mechanism
- Can identify appropriate use cases

Follow-up: How would you handle adding a new element type without changing all visitors?


Pattern Selection

23.

How to choose the right pattern?

How do you decide which design pattern to use for a given problem?

Senior

Pattern Selection Process:

1. Identify the Problem Category

Problem Patterns to Consider
Object creation complexity Factory, Builder, Prototype
Algorithm variation Strategy, Template Method
State-dependent behavior State, Strategy
Structure adaptation Adapter, Decorator, Proxy
Complex object structure Composite, Flyweight
Object communication Observer, Mediator, Command
Traversing structures Iterator, Visitor

2. Ask Key Questions

Creation Problems:
- "Does object creation vary?" → Factory
- "Many optional parameters?" → Builder
- "Expensive to create, need copies?" → Prototype

Behavioral Problems:
- "Behavior changes at runtime?" → Strategy
- "Object state affects behavior?" → State
- "Need to undo/queue operations?" → Command
- "One-to-many notifications?" → Observer

Structural Problems:
- "Incompatible interfaces?" → Adapter
- "Add responsibilities dynamically?" → Decorator
- "Control access to object?" → Proxy
- "Simplify complex subsystem?" → Facade

3. Consider Trade-offs

Pattern vs Complexity:
- Simple problem → No pattern needed
- Medium problem → Single pattern
- Complex problem → Multiple patterns

Pattern vs Flexibility:
- High flexibility → More abstraction
- Low flexibility → Simpler code

4. Red Flags - When NOT to Use Patterns

  • "I want to use a pattern" (pattern hunting)
  • Only one concrete implementation likely
  • Simple conditional would suffice
  • Team doesn't know the pattern
  • Problem doesn't match pattern intent

Decision Tree Example:

Need to create objects?
├── Yes, complex construction? → Builder
├── Yes, family of objects? → Abstract Factory
├── Yes, single type varies? → Factory Method
└── No

Need to add behavior?
├── Yes, at runtime? → Decorator
├── Yes, different algorithms? → Strategy
└── No

Need to adapt interfaces?
├── Yes, incompatible? → Adapter
├── Yes, simplify? → Facade
└── No

Key Points to Look For:
- Problem-first approach
- Considers alternatives
- Knows when not to use

Follow-up: Can you give an example where you chose NOT to use a pattern?

24.

Anti-patterns: what to avoid

What are common anti-patterns in software design?

Mid

Anti-patterns are common solutions that seem good but cause problems.

1. God Object / God Class

// Everything in one class
class Application {
    void handleUserLogin() { }
    void processPayment() { }
    void sendEmail() { }
    void generateReport() { }
    void backupDatabase() { }
}

Fix: Single Responsibility, extract classes

2. Singleton Abuse

// Global state everywhere
UserSession.getInstance().getUser();
Config.getInstance().getValue();
Logger.getInstance().log();

Fix: Dependency Injection

3. Golden Hammer
Using same solution for every problem.

"We use microservices for everything"
"Everything is a Factory"

Fix: Choose tools for the job

4. Cargo Cult Programming

// Copy-paste without understanding
// "We always do it this way"
try {
    // code
} catch (Exception e) {
    // What was this for again?
}

Fix: Understand before using

5. Lava Flow
Dead code that no one dares to remove.

// TODO: Remove after migration (2018)
void oldMethod() {
    // Is this still used???
}

Fix: Test coverage, brave deletion

6. Spaghetti Code
Tangled control flow, no clear structure.
Fix: Refactoring, separation of concerns

7. Copy-Paste Programming

// Same code in 5 places, slightly different

Fix: DRY, extract common logic

8. Premature Optimization

// Complex caching before profiling
// "This might be slow"

Fix: Profile first, optimize after

9. Magic Numbers/Strings

if (status == 4) { }  // What is 4?
if (type.equals("A")) { }  // What is A?

Fix: Named constants, enums

10. Feature Envy
Method more interested in another class's data.

void calculatePrice(Order order) {
    return order.getQuantity() * order.getProduct().getPrice() *
           (1 - order.getCustomer().getDiscount());
}

Fix: Tell Don't Ask, move to Order

Key Points to Look For:
- Knows multiple anti-patterns
- Can explain why they're harmful
- Knows remedies

Follow-up: How do you address anti-patterns in legacy code?

25.

Patterns in modern frameworks

Give examples of design patterns used in popular frameworks.

Mid

Spring Framework:

Pattern Usage
Singleton Default bean scope
Factory BeanFactory, ApplicationContext
Proxy AOP, @Transactional
Template Method JdbcTemplate, RestTemplate
Strategy Resource loaders
Observer ApplicationEvent, @EventListener
Decorator BeanPostProcessor
// Template Method in JdbcTemplate
jdbcTemplate.query(sql, (rs, rowNum) -> {
    // You provide the mapping
    return new User(rs.getString("name"));
});

React:

Pattern Usage
Composite Component tree
Observer State updates, hooks
Strategy Render props
Decorator Higher-Order Components
Flyweight Virtual DOM reconciliation
// Composite - components contain components
<App>
  <Header />
  <Main>
    <Sidebar />
    <Content />
  </Main>
</App>

Java Collections:

Pattern Usage
Iterator Collection.iterator()
Factory Collections.emptyList()
Decorator Collections.synchronized*()
Strategy Comparator
// Decorator pattern
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

// Strategy pattern
list.sort(Comparator.comparing(User::getName));

Servlet/Web:

Pattern Usage
Chain of Responsibility Filter chain
Front Controller DispatcherServlet
MVC Spring MVC

JUnit:

Pattern Usage
Template Method @Before, @After hooks
Command Test as runnable command
Composite Test suites

Key Points to Look For:
- Knows patterns in frameworks they use
- Can explain how pattern is applied
- Recognizes patterns in code

Follow-up: How does understanding these patterns help when debugging?