Part 2 ~ Top 10 Low-Level Design Questions

A beginner-friendly summary of Low-Level Design concepts from coupling to Factory Pattern. Let’s dive in!

Uqie Rachmadie

Uqierach

6/8/2026 · 17 min read

Part 2 ~ Top 10 Low-Level Design Questions

Part 2 ~ Top 10 Low-Level Design Questions

These are my personal notes after reading about common Low-Level Design concepts. I rewrote them in a simpler way so they're easier to understand instead of just memorizing interview answers.


11. Why is Loose Coupling Better Than Tight Coupling?

One of the main goals in Low-Level Design is making components independent from each other.

When two classes are tightly coupled, changing one class usually forces you to modify the other. As the project grows, this quickly becomes difficult to maintain.

On the other hand, loose coupling allows components to communicate through abstractions like interfaces. Each component only knows what another component can do, not how it does it.

Benefits of loose coupling:

  • Easier to replace implementations
  • Better unit testing
  • More maintainable code
  • Easier to extend new features

Example

Imagine a payment service.

Instead of creating a PayPalPayment directly, the service depends on a PaymentProvider interface.

Later, adding Stripe or Midtrans doesn't require changing the business logic.

---
config:
  look: handDrawn
---
classDiagram

class PaymentService
class PaymentProvider{
    <>
    +pay()
}

class Paypal
class Stripe

PaymentService --> PaymentProvider
PaymentProvider <|.. Paypal
PaymentProvider <|.. Stripe

This is one of the core ideas behind SOLID principles, especially the Dependency Inversion Principle.


12. What Are Design Patterns?

Design Patterns are proven solutions to problems that appear repeatedly in software development.

They aren't ready-to-use code, but rather templates or best practices for solving common design challenges.

Using design patterns helps developers:

  • Write cleaner code
  • Improve consistency across a project
  • Reduce duplicated logic
  • Make applications easier to extend

Think of them as architectural blueprints.

You don't copy the blueprint exactly, but you adapt it based on your project's needs.

---
config:
  look: handDrawn
---
flowchart TD

A[Design Patterns]

A --> B[Creational]
A --> C[Structural]
A --> D[Behavioral]

B --> Factory
B --> Singleton

C --> Adapter
C --> Decorator

D --> Strategy
D --> Observer

Some of the most popular patterns you'll encounter are Factory, Singleton, Strategy, Observer, and Builder.


13. When Should You Use the Singleton Pattern?

Sometimes an application only needs one instance of a class.

Instead of creating multiple objects, the Singleton Pattern ensures that only a single instance exists and everyone shares it.

Common use cases include:

  • Application configuration
  • Logging service
  • Database connection manager
  • Cache manager

Imagine if every part of your application created its own logger or configuration object. Besides wasting memory, different instances could hold inconsistent states.

---
config:
  look: handDrawn
---
flowchart LR

ClientA --> Singleton
ClientB --> Singleton
ClientC --> Singleton

Singleton --> Instance[(Single Object)]

Although Singleton is useful, avoid overusing it.

Having too many global objects can make applications harder to test because different modules silently depend on shared state.


14. How Does the Observer Pattern Work?

The Observer Pattern allows one object to notify multiple objects whenever something changes.

Instead of checking continuously for updates, observers automatically receive notifications from the subject.

This creates a one-to-many relationship while keeping components loosely coupled.

Real-world examples include:

  • Stock price updates
  • Chat applications
  • Email subscriptions
  • GUI event listeners
  • Notification systems

Imagine a stock trading application.

When the stock price changes, every trader dashboard should update immediately.

Instead of each dashboard polling the server every second, the stock object simply broadcasts an update.

---
config:
  look: handDrawn
---
sequenceDiagram

participant Stock
participant Trader A
participant Trader B
participant Dashboard

Stock->>Trader A: Price Updated
Stock->>Trader B: Price Updated
Stock->>Dashboard: Price Updated

Observer is commonly used in event-driven systems where many components react to the same event.


15. Why Use the Factory Pattern?

Creating objects directly with new works fine for small applications.

However, larger systems often need different object implementations depending on runtime conditions.

The Factory Pattern centralizes object creation so the client doesn't need to know which concrete class should be instantiated.

Benefits include:

  • Less duplicated creation logic
  • Easier to add new implementations
  • Better separation of responsibilities
  • Supports loose coupling

Example

Suppose an e-commerce application supports several payment methods.

Instead of writing:

new PaypalPayment();
new CreditCardPayment();
new BankTransferPayment();

The application simply asks the factory:

Payment payment = PaymentFactory.create(type);

The factory decides which object should be returned.

---
config:
  look: handDrawn
---
classDiagram

class Client
class PaymentFactory

class Payment{
    <>
    +pay()
}

class Paypal
class Card
class Wallet

Client --> PaymentFactory
PaymentFactory --> Payment

Payment <|.. Paypal
Payment <|.. Card
Payment <|.. Wallet

The Factory Pattern becomes especially useful when object creation is complex or depends on configuration, user input, or environment variables.

Instead of spreading creation logic throughout the application, everything stays in one place.


16. Why Should You Use the Strategy Pattern?

Sometimes an application needs multiple ways to perform the same task.

Instead of putting every algorithm inside one class with lots of if-else or switch statements, the Strategy Pattern separates each algorithm into its own class.

This makes the code easier to maintain and allows behavior to change at runtime.

Benefits include:

  • Removes long conditional statements
  • Makes algorithms interchangeable
  • Easier to add new behaviors
  • Follows the Open/Closed Principle

Example

Imagine an online payment system.

A customer may choose:

  • Credit Card
  • PayPal
  • Bank Transfer
  • E-Wallet

Each payment method has its own implementation, but the checkout process always uses the same interface.

---
config:
  look: handDrawn
---
classDiagram

class CheckoutService

class PaymentStrategy{
    <>
    +pay()
}

class CreditCard
class Paypal
class BankTransfer

CheckoutService --> PaymentStrategy

PaymentStrategy <|.. CreditCard
PaymentStrategy <|.. Paypal
PaymentStrategy <|.. BankTransfer

Instead of changing the checkout logic every time a new payment method is introduced, you simply create another strategy.


17. Why Are Interfaces Important in Low-Level Design?

Interfaces define what an object can do, without specifying how it does it.

They allow different implementations to share the same contract, making applications much more flexible.

Instead of depending on concrete classes, your code depends on abstractions.

Advantages include:

  • Easier to swap implementations
  • Better unit testing with mocks
  • Supports Dependency Injection
  • Reduces coupling between modules

Example

Suppose your application sends notifications.

Rather than calling an email service directly, your application communicates through a Notification interface.

Different implementations can then be plugged in whenever needed.

InterfacePossible Implementations
NotificationEmail
NotificationSMS
NotificationPush Notification
NotificationWhatsApp

This approach allows new notification channels to be added without modifying existing business logic.


18. How Do You Choose the Right Sorting Algorithm?

There isn't a single sorting algorithm that's best for every situation.

The right choice depends on your data and system requirements.

Things to consider include:

  • Dataset size
  • Available memory
  • Data distribution
  • Stability requirement
  • Execution speed
  • Whether data is stored in memory or on disk

For example, sorting one thousand records is very different from sorting hundreds of gigabytes of data.

Common Choices

SituationRecommended AlgorithmReason
General-purpose sortingQuickSortVery fast average performance
Stable sortingMerge SortKeeps equal elements in order
Nearly sorted dataInsertion SortMinimal overhead
Huge datasets on diskExternal Merge SortDoesn't require loading everything into memory
Small datasetsInsertion SortSimple and efficient

Choosing the right algorithm is about understanding the trade-offs instead of memorizing time complexities.


19. How Can You Evolve an API Without Breaking Existing Clients?

Software rarely stays the same forever.

As applications grow, APIs need new features, additional fields, or improved behavior. The challenge is introducing those changes without breaking applications that already depend on the old version.

This is where versioning and backward compatibility become important.

Common practices include:

  • Versioning APIs (v1, v2)
  • Database migrations
  • Feature flags
  • Gradual deprecation
  • Regression testing

Example

Suppose version 1 returns:

{
  "name": "Uqie"
}

Version 2 introduces a new field:

{
  "name": "Uqie",
  "avatar": "profile.png"
}

Instead of replacing the old endpoint immediately, both versions continue running until clients finish migrating.

---
config:
  look: handDrawn
---
flowchart LR

ClientA --> V1["/api/v1/users"]
ClientB --> V2["/api/v2/users"]

V1 --> Database
V2 --> Database

This strategy allows new applications to use the latest API while older applications continue working normally.


20. How Does Authentication and Authorization Work in Distributed Systems?

Authentication and authorization solve two different problems.

  • Authentication answers: Who are you?
  • Authorization answers: What are you allowed to do?

Modern distributed systems usually separate these responsibilities using an Identity Provider and token-based authentication.

A common workflow looks like this:

  1. User logs in.
  2. Identity Provider verifies the credentials.
  3. A JWT or access token is generated.
  4. The client includes the token in every request.
  5. Each service validates the token before processing the request.

Popular technologies include:

  • OAuth 2.0
  • OpenID Connect
  • JWT
  • Multi-Factor Authentication (MFA)
  • Role-Based Access Control (RBAC)
---
config:
  look: handDrawn
---
sequenceDiagram

actor User
participant IdentityProvider
participant API Gateway
participant Order Service

User->>IdentityProvider: Login
IdentityProvider-->>User: JWT Token

User->>API Gateway: Request + JWT
API Gateway->>Order Service: Forward Request

Order Service->>Order Service: Validate Token
Order Service-->>User: Authorized Response

In a microservices architecture, every service validates the token independently before serving the request. This keeps services secure while allowing users to authenticate only once.


Final Thoughts

As you learn more about Low-Level Design, you'll notice that many concepts are connected.

For example:

  • Interfaces help achieve loose coupling.
  • Factory creates objects.
  • Strategy changes behavior.
  • Observer distributes events.
  • Singleton manages shared resources.

Learning each pattern individually is useful, but understanding when and why to use them is what really matters.

A good software design isn't about using every design pattern—it’s about choosing the simplest solution that solves the problem while keeping the code maintainable and easy to extend.