Bird
Raised Fist0

Design a Logging Framework - Log Levels, Appenders, Formatters, Log Rotation

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
▶
Class Trace
Step 1/4
Logger Singleton

Logger is a Singleton with a volatile static instance and synchronized getInstance method to ensure thread-safe lazy initialization.

🎯 Tests whether candidate knows volatile prevents JVM reordering allowing safe publication of Logger instance and synchronized ensures atomic check-and-set.
public class Logger {
    private static volatile Logger instance;
    private List<Appender> appenders = new ArrayList<>();
    private Logger() {}
    public static Logger getInstance() {
        if (instance == null) {
            synchronized (Logger.class) {
                if (instance == null) {
                    instance = new Logger();
                }
            }
        }
        return instance;
    }
    public synchronized void log(String message) {
        for (Appender appender : appenders) {
            appender.append(message);
        }
    }
}
Appender Abstract Class and Composition

Appender is an abstract class holding a Formatter reference, showing composition because Appender owns Formatter lifecycle and manages formatting behavior.

🎯 Tests whether candidate understands abstract class is used here to hold shared state (Formatter) which interfaces cannot hold in Java, enforcing OCP by allowing new Appenders without modifying Logger.
public abstract class Appender {
    protected Formatter formatter;
    public Appender(Formatter formatter) {
        this.formatter = formatter;
    }
    public abstract synchronized void append(String message);
}
Formatter Interface and Realization

Formatter is a Java interface with no state, implemented by SimpleFormatter and PatternFormatter, demonstrating Strategy pattern for formatting.

🎯 Tests whether candidate knows interface cannot hold state in Java and realizes OCP by allowing new formatting strategies without changing Appender or Logger.
public interface Formatter {
    String format(String message);
}
public class SimpleFormatter implements Formatter {
    public String format(String message) {
        return "[Simple] " + message;
    }
}
public class PatternFormatter implements Formatter {
    public String format(String message) {
        // complex pattern formatting logic
        return "[Pattern] " + message;
    }
}
FileAppender extends Appender with Log Rotation

FileAppender extends Appender adding synchronized rotateLogs method to handle log rotation safely in concurrent environments.

🎯 Tests whether candidate understands synchronized methods protect shared mutable state (log files) from race conditions during rotation.
public class FileAppender extends Appender {
    public FileAppender(Formatter formatter) {
        super(formatter);
    }
    @Override
    public synchronized void append(String message) {
        // write formatted message to file
    }
    public synchronized void rotateLogs() {
        // rotate log files safely
    }
}
📐
Class Diagram
Class Diagram
       +----------------+
       | <<Singleton>>  |
       |    Logger      |
       |----------------|
       | - volatile     |
       |   instance     |
       | - List<App>    |
       |----------------|
       | + getInst():   |
       |   Logger       |
       | + log(String): |
       |   void [sync]  |
       +----------------+
              │
              │ ◆ composition (Logger owns Appenders lifecycle)
              │
       +----------------+
       | <<Abstract>>   |
       |   Appender     |
       |----------------|
       | - Formatter    |
       |----------------|
       | + append(String):|
       |   void [sync]  |
       +----------------+
              │
              │ │ extends
              │ │
       +----------------+
       | ConsoleAppender|
       |----------------|
       | + append(String):|
       |   void [sync]  |
       +----------------+
              │
              │ │ extends
              │ │
       +----------------+
       | FileAppender   |
       |----------------|
       | + append(String):|
       |   void [sync]  |
       | + rotateLogs():|
       |   void [sync]  |
       +----------------+
              │
              │ ◆ composition (Appender owns Formatter lifecycle)
              │
       +----------------+
       | <<Interface>>  |
       |   Formatter    |
       |----------------|
       | + format(String):|
       |   String       |
       +----------------+
              │
              │ ─ ─ ─► realization
              │
       +----------------+
       | SimpleFormatter|
       |----------------|
       | + format(String):|
       |   String       |
       +----------------+
              │
              │ ─ ─ ─► realization
              │
       +----------------+
       | PatternFormatter|
       |----------------|
       | + format(String):|
       |   String       |
       +----------------+

This class diagram illustrates key Java design decisions in a logging framework. Logger is a thread-safe Singleton using volatile and synchronized to ensure safe lazy initialization. Logger composes multiple Appenders, which are abstract classes holding Formatter references, enforcing composition lifecycle ownership and enabling shared state. Formatter is a pure interface with no state, realized by SimpleFormatter and PatternFormatter, demonstrating the Strategy pattern and Open-Closed Principle by allowing new formatting behaviors without modifying existing code. FileAppender extends Appender adding synchronized methods for log rotation, ensuring thread-safe mutation of log files. These decisions highlight Java-specific concurrency mechanisms, state vs behavior separation, and SOLID principles in extensible logging design.

Primary Focus

✓ Abstract class vs Interface for shared state + Composition lifecycle + Realization + Singleton volatile

Practice

(1/5)
1. In designing the User and Message classes for the messaging system, should User be an abstract class or an interface, and why?
easy
A. User should be an interface to allow multiple inheritance of user behaviors.
B. User should be an interface because it only defines behavior without state.
C. User should be an abstract class because it holds common user state like userId and username.
D. User should be an abstract class to enforce implementation of all methods in subclasses.

Solution

  1. Step 1: Analyze if User has state or just behavior

    User holds common state such as userId and username, which requires fields and possibly default implementations.
  2. Step 2: Determine if abstract class or interface fits state and behavior

    Abstract class supports shared state and partial implementation, while interface cannot hold instance state.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    User has common state -> abstract class fits better than interface [check]
Hint: Stateful base class -> abstract class, stateless -> interface [check]
Common Mistakes:
2. In designing a ProductVariant interface versus an abstract class for an e-commerce catalog, which is the better choice and why?
easy
A. Use an abstract class to allow multiple inheritance of variant features
B. Use an interface because variants differ widely and only share behavior contracts
C. Use an abstract class because variants share common state like SKU and price
D. Use an interface to store shared state variables across variants

Solution

  1. Step 1: Analyze state vs behavior sharing

    Product variants share common state such as SKU, price, and inventory count, which is best encapsulated in an abstract class to avoid code duplication.
  2. Step 2: Why interface is less suitable

    Interfaces define behavior contracts but cannot hold state, so using an interface alone would force each variant to reimplement common fields, violating DRY.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Shared state -> abstract class; behavior only -> interface [check]
Hint: Shared state needs abstract class, not interface [check]
Common Mistakes:
3. In the Elevator System, should the Elevator class implement an interface or extend an abstract class to represent different elevator types (e.g., passenger, freight)? Consider that elevators share common state and behavior but also have type-specific logic.
easy
A. Use an abstract class, because elevators share common state and partial behavior implementations.
B. Use an interface, because elevators only share behavior signatures, not state.
C. Use a concrete class, because inheritance is unnecessary for elevator types.
D. Use multiple interfaces, one per behavior, to avoid state duplication.

Solution

  1. Step 1: Analyze shared state and behavior

    Elevators share common fields like currentFloor, direction, and doorStatus, plus some implemented methods.
  2. Step 2: Abstract class fits shared state and partial behavior

    Abstract class allows code reuse and enforces common state, while subclasses override type-specific logic.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Abstract class supports shared state and behavior [check]
Hint: Abstract class for shared state; interface for pure behavior [check]
Common Mistakes:
4. In the shopping cart system, should Discount be designed as an abstract class or an interface? Consider that discounts have varying calculation logic but share no common state.
easy
A. Interface, because Java interfaces can hold state and behavior since Java 8.
B. Abstract class, because discounts share common fields like discountAmount.
C. Abstract class, to enforce a default implementation of applyDiscount().
D. Interface, because discounts only define calculation behavior without shared state.

Solution

  1. Step 1: Analyze shared state vs behavior

    Discounts vary in calculation logic and do not share common fields, so no shared state exists.
  2. Step 2: Choose interface for behavior without state

    Interfaces define behavior contracts without enforcing state, fitting varying discount calculations better than abstract classes.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    No shared state + varying behavior -> interface [check]
Hint: No shared state + varying logic -> interface [check]
Common Mistakes:
5. In the Food Ordering System, the KitchenQueue class has a parkOrder() method that checks if a slot is available before assigning an order. Without synchronization on parkOrder(), which exact thread interleaving causes double-booking of the same slot?
medium
A. Thread 1 and Thread 2 both call parkOrder() simultaneously and the JVM throws an exception.
B. Thread 1 checks slot availability -> true, context switch before assignment; Thread 2 checks availability -> true, both assign same slot.
C. Thread 1 assigns slot, then Thread 2 waits for Thread 1 to finish before assigning next slot.
D. Thread 1 and Thread 2 both wait on a lock, causing deadlock and no assignment.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 reads slot as available but is preempted before assignment; Thread 2 also reads available and proceeds.
  2. Step 2: Both threads assign the same slot leading to double-booking

    Because the availability check and assignment are not atomic, both threads assign the same slot concurrently.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Check-then-act gap without sync -> double assignment [check]
Hint: Check availability -> context switch -> both assign same slot [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard