Financial System Design Patterns Interview Notes
About
This page is a consolidated reference for understanding and revisiting software design patterns through real-world financial system examples. The focus is not only on theoretical definitions, but also on how these patterns are practically applied in enterprise backend systems such as payment platforms, banking applications, and microservices architectures.
Creational
Singleton
Singleton ensures a single instance with global access. In Java, thread safety is achieved using double-checked locking with volatile or Bill Pugh pattern. However, in modern Spring Boot applications, Singleton is managed by the Spring container using default bean scope. We used it for caching exchange rates and configuration to maintain consistency across payment flows. One must be careful as Singleton can be broken via reflection or serialization and can hurt testability if not used with dependency injection
Without Singleton:
Multiple DB connection managers → inconsistent state
Multiple cache instances → memory issues
Multiple config loaders → bugs
Spring Boot Reality
You usually DON’T implement Singleton manually
👉 Spring beans are Singleton by default:
@Service
public class ExchangeRateService {}Spring ensures:
Only one instance exists
Thread-safe lifecycle
Use Case: Central Configuration Manager
In banking/payment system:
Exchange rate config
Feature flags (IMPS enabled, UPI limits)
AML rules
Must be single source of truth
In the Double-Checked Locking singleton pattern, volatile is extremely important.
Without volatile, the code is broken:
It may work most of the time, but under concurrency another thread can get a partially initialized object.
Why?
This line:
is not actually a single atomic operation.
Internally JVM may do:
Allocate memory
Assign memory reference to
instanceExecute constructor
Due to JVM/compiler/CPU optimizations, steps 2 and 3 can be reordered.
So actual execution may become:
Allocate memory
Assign reference to
instanceConstructor executes
Now imagine:
Thread A:
Thread B:
Since reference assignment already happened, Thread B sees non-null instance and returns it.
But constructor may not have completed yet.
So Thread B gets a partially initialized object.
volatile provides:
Visibility guarantee
One thread's updates are immediately visible to others.
Prevents instruction reordering
JVM cannot reorder constructor completion after reference assignment.
So JVM must guarantee:
Allocate memory
Run constructor fully
Assign reference to
instance
Only after complete construction can another thread see the object.
Question
Where did you use Singleton in your project?
"In Spring Boot, most services are Singleton by default. For example, we used a singleton cache for exchange rates and feature configurations to ensure consistency across all payment flows and avoid repeated external API calls."
How to Make Singleton Thread-Safe?
How Can Singleton Be Broken?
Spring Singleton vs GoF Singleton
Scope
JVM-wide
Spring container
Creation
Manual
Framework-managed
Testability
Hard
Easy
Flexibility
Low
High
Factory Method Pattern
Factory pattern encapsulates object creation logic and removes tight coupling from client code. In our payment system, we used a factory to route transactions dynamically to UPI, IMPS, or NEFT processors. With Spring Boot, we leveraged map-based injection of beans to make it extensible without modifying existing code.
Define an interface for creating an object, but let subclasses decide which class to instantiate.
Problem It Solves ?
Without Factory:
❌ Issues:
Tight coupling
Violates Open/Closed Principle
Hard to extend (add NEFT → modify code everywhere)
✅ With Factory Pattern
Client doesn’t care which implementation
Use Case
💳 Payment Routing System
A bank supports:
UPI
IMPS
NEFT
RTGS
Each has:
different APIs
different validations
different SLAs
Factory decides which processor to use
🧱 Structure
Example
Question
Factory vs Strategy (VERY COMMON)
Creates object
Uses object
Focus on instantiation
Focus on behavior
Happens once
Used repeatedly
Together in real systems:
Factory → chooses processor
Strategy → processor executes logic
Abstract Factory Pattern
Abstract Factory is used to create families of related objects. In our system, we used it for multi-country payment processing where each country had its own processor, validator, and fee calculator. This ensured consistency and prevented mixing incompatible components while keeping the system extensible.
Problem It Solves ?
Let’s say we’re building a multi-country payment system:
Each country has:
Payment Processor
Validator
Fee Calculator
Without Abstract Factory:
❌ Problem:
Risk of mixing objects (India processor + US validator = bug)
Tight coupling
Hard to scale
Solution: Abstract Factory
👉 Ensure consistent object family creation
🧱 Structure
Example
Factory vs Abstract Factory
Creates
One object
Multiple related objects
Example
PaymentProcessor
Processor + Validator + Fee
Scope
Single product
Product family
Builder Pattern
Builder pattern is used to construct complex objects step-by-step, especially when there are many optional parameters. In our payment system, we used Builder for creating request objects to ensure immutability, readability, and validation before object creation.
👉 In simple terms:
“Build complex objects step-by-step instead of using huge constructors.”
👉 Builder is commonly used to create immutable objects since fields are final
Problem It Solves ?
❌ Telescoping Constructor Problem
Issues:
Hard to read
Order matters (bug-prone)
Optional fields messy
Not maintainable
✅ Builder Solution
👉 Clean, readable, safe
Structure
Example
Builder vs Constructor
Hard to read
Readable
Order matters
Named fields
Poor for optional fields
Excellent
Builder vs Factory
Builds complex object step-by-step
Decides which object to create
Same class
Multiple classes
Prototype Pattern
Create new objects by copying (cloning) an existing object instead of creating from scratch.
👉 In simple terms:
“Duplicate an existing object instead of building a new one.”
Problem It Solves ?
❌ Expensive Object Creation
Imagine:
DB-heavy object initialization
complex config loading
large nested objects
👉 Creating repeatedly = costly
✅ Solution: Clone Existing Object
👉 Fast + efficient
Last updated