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

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:

  1. Allocate memory

  2. Assign memory reference to instance

  3. Execute constructor

Due to JVM/compiler/CPU optimizations, steps 2 and 3 can be reordered.

So actual execution may become:

  1. Allocate memory

  2. Assign reference to instance

  3. Constructor 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:

  1. Visibility guarantee

    • One thread's updates are immediately visible to others.

  2. Prevents instruction reordering

    • JVM cannot reorder constructor completion after reference assignment.

So JVM must guarantee:

  1. Allocate memory

  2. Run constructor fully

  3. 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

Aspect
GoF Singleton
Spring 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)

Factory
Strategy

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

Feature
Factory
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

Constructor
Builder

Hard to read

Readable

Order matters

Named fields

Poor for optional fields

Excellent

Builder vs Factory

Builder
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