Java & React Knowledge Review

71 min read
Java & React Knowledge Review

Technical Interview Preparation Guide

A comprehensive guide covering React, TypeScript, CI/CD, Microservices, Cloud Architecture, and more.


Table of Contents

  1. React Fundamentals
  2. ES6 JavaScript vs TypeScript
  3. Accessibility Best Practices
  4. CI Steps for Front-End
  5. RESTful API Characteristics
  6. Java BFF Benefits
  7. Scrum/Agile for Quality
  8. React Re-render Optimization
  9. Jest vs Selenium/E2E Testing
  10. GraphQL vs REST
  11. Microservice Design Characteristics
  12. Test Coverage and Mocking
  13. CI/CD Pipeline Standard
  14. AWS Deployment for React & Java
  15. Cloud-First Banking Architecture
  16. Resilience Patterns in Java BFF
  17. API Security Best Practices
  18. Distributed Transactions & Saga
  19. Quality Gates for Financial Systems
  20. Performance Balancing Strategy

1. React Fundamentals

Components

  • Definition: Reusable, self-contained building blocks of a React UI
  • Types:
    • Functional Components: JavaScript functions that return JSX (preferred in modern React)
    • Class Components: ES6 classes extending React.Component (legacy)
  • Role: Encapsulate UI logic, structure, and behavior

Props (Properties)

  • Definition: Read-only data passed from parent to child components
  • Characteristics:
    • Immutable within the receiving component
    • Enable component reusability and customization
    • Flow unidirectionally (top-down)
  • Use Cases: Configuration, callbacks, data passing

State

  • Definition: Mutable data managed within a component
  • Characteristics:
    • Changes trigger re-renders
    • Local and encapsulated to the component
    • Managed via useState hook (functional) or this.setState (class)
  • Use Cases: User input, toggle states, fetched data

When to Split a Component

SignalAction
Component exceeds 200-300 linesExtract logical sections
Multiple responsibilitiesSingle Responsibility Principle
Repeated JSX patternsCreate reusable component
Complex nested conditionalsExtract into smaller components
Different update frequenciesSeparate to optimize renders
Shared logic across componentsCreate custom hooks
// Before: Monolithic component
function Dashboard() {
  return (
    <div>
      {/* Header logic */}
      {/* Sidebar logic */}
      {/* Main content logic */}
      {/* Footer logic */}
    </div>
  );
}

// After: Split components
function Dashboard() {
  return (
    <div>
      <Header />
      <Sidebar />
      <MainContent />
      <Footer />
    </div>
  );
}

2. ES6 JavaScript vs TypeScript

FeatureES6 JavaScriptTypeScript
Type SystemDynamic typingStatic typing with type annotations
Compile-time ErrorsNone (runtime only)Catches errors at compile time
IDE SupportBasic autocompleteRich IntelliSense, refactoring
InterfacesNot supportedFull interface support
GenericsNot supportedFull generic support
EnumsNot nativeNative enum support
Access ModifiersNot supportedpublic, private, protected
Build StepOptional (Babel)Required (tsc compiler)
Learning CurveLowerHigher (but manageable)
RefactoringError-proneSafe with type checking

TypeScript Benefits in React

// Type-safe props
interface UserCardProps {
  name: string;
  age: number;
  email?: string; // Optional
  onUpdate: (id: string) => void;
}

const UserCard: React.FC<UserCardProps> = ({ name, age, email, onUpdate }) => {
  // TypeScript ensures correct prop usage
  return <div>{name}</div>;
};

// Type-safe state
const [user, setUser] = useState<User | null>(null);

3. Accessibility Best Practices

1. Semantic HTML

// ❌ Bad
<div onClick={handleClick}>Click me</div>

// ✅ Good
<button onClick={handleClick}>Click me</button>

2. ARIA Labels and Roles

// Provide context for screen readers
<button aria-label="Close dialog" onClick={onClose}>
  <CloseIcon />
</button>

<nav role="navigation" aria-label="Main navigation">
  {/* Navigation items */}
</nav>

// Live regions for dynamic content
<div aria-live="polite" aria-atomic="true">
  {statusMessage}
</div>

3. Keyboard Navigation

// Ensure all interactive elements are focusable
<div 
  tabIndex={0}
  onKeyDown={(e) => e.key === 'Enter' && handleAction()}
  role="button"
>
  Interactive Element
</div>

// Focus management in modals
useEffect(() => {
  if (isOpen) {
    modalRef.current?.focus();
  }
}, [isOpen]);

Additional Best Practices

  • Color Contrast: Minimum 4.5:1 ratio for normal text
  • Alt Text: Meaningful descriptions for images
  • Form Labels: Associate labels with inputs using htmlFor/id
  • Skip Links: Allow users to skip repetitive navigation
  • Focus Indicators: Visible focus states for all interactive elements

4. CI Steps for Front-End

Minimal CI Pipeline Steps

# .github/workflows/frontend-ci.yml
name: Frontend CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # 1. Checkout & Setup
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      # 2. Install Dependencies
      - name: Install dependencies
        run: npm ci

      # 3. Linting
      - name: Lint code
        run: npm run lint

      # 4. Type Checking (TypeScript)
      - name: Type check
        run: npm run type-check

      # 5. Unit Tests
      - name: Run tests
        run: npm run test:ci

      # 6. Build
      - name: Build application
        run: npm run build

      # 7. (Optional) Bundle Analysis
      - name: Analyze bundle
        run: npm run analyze

Summary of Steps

  1. Dependency Installation: npm ci for reproducible builds
  2. Linting: ESLint for code quality
  3. Type Checking: TypeScript compilation check
  4. Unit/Component Tests: Jest + React Testing Library
  5. Build: Production build verification
  6. Optional: Bundle size analysis, security audit (npm audit)

5. RESTful API Characteristics

Core Principles

CharacteristicDescription
StatelessEach request contains all needed information
Resource-BasedURLs represent resources, not actions
HTTP MethodsProper use of GET, POST, PUT, PATCH, DELETE
Uniform InterfaceConsistent URL patterns and response formats
HATEOASHypermedia links for discoverability

Best Practices

# Resource Naming (Nouns, not verbs)
GET    /api/v1/users          # List users
GET    /api/v1/users/{id}     # Get specific user
POST   /api/v1/users          # Create user
PUT    /api/v1/users/{id}     # Replace user
PATCH  /api/v1/users/{id}     # Partial update
DELETE /api/v1/users/{id}     # Delete user

# Nested Resources
GET    /api/v1/users/{id}/orders

Response Standards

  • Consistent Structure: { data, meta, errors }
  • HTTP Status Codes: 200, 201, 204, 400, 401, 403, 404, 500
  • Pagination: ?page=1&limit=20
  • Filtering: ?status=active&sort=-createdAt
  • Versioning: /api/v1/ or via headers

6. Java BFF Benefits

Why and When Was BFF Introduced?

The BFF (Backend for Frontend) pattern was coined by Sam Newman (author of "Building Microservices") at SoundCloud around 2015. It emerged as a solution to problems that arose when microservices architectures became popular.

The Problem Before BFF

In traditional microservices architecture, frontends had to deal with multiple backend services directly:

                    ┌─────────────────┐
                    │   Web Browser   │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │                    │                    │
        ▼                    ▼                    ▼
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│ User Service  │   │ Order Service │   │ Product Service│
└───────────────┘   └───────────────┘   └───────────────┘

Problems This Caused:

  1. Multiple API calls: Frontend needed 5-10 calls to render one page
  2. Over-fetching: APIs returned data designed for all clients, not specific needs
  3. Under-fetching: Missing data required additional calls
  4. Complex frontend logic: Data aggregation and transformation in browser
  5. Different client needs: Mobile vs Web vs Smart TV needed different data
  6. Security exposure: Internal service details leaked to clients
  7. Tight coupling: Frontend changes required backend changes

What is BFF (Backend for Frontend)?

A dedicated backend layer tailored to specific frontend needs.

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Web Browser │     │ Mobile App  │     │  Smart TV   │
└──────┬──────┘     └──────┬──────┘     └──────┬──────┘
       │                   │                   │
       ▼                   ▼                   ▼
┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│   Web BFF    │   │  Mobile BFF  │   │    TV BFF    │
└──────┬───────┘   └──────┬───────┘   └──────┬───────┘
       │                   │                   │
       └───────────────────┼───────────────────┘
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
        ▼                  ▼                  ▼
┌───────────────┐  ┌───────────────┐  ┌───────────────┐
│ User Service  │  │ Order Service │  │Product Service│
└───────────────┘  └───────────────┘  └───────────────┘

When to Introduce BFF

ScenarioBFF Needed?
Single frontend, simple CRUD❌ No
Multiple clients (web, mobile, TV) with different needs✅ Yes
Need to aggregate data from many microservices✅ Yes
Frontend making 5+ API calls per page✅ Yes
Security concerns (hiding internal APIs)✅ Yes
Different teams own frontend and backend✅ Yes
Need caching/rate limiting per client type✅ Yes
Simple monolith with one client❌ No

Real-World Example: SoundCloud

Before BFF:

Mobile App needs to show user profile page:
1. GET /users/123           → User details
2. GET /users/123/tracks    → User's tracks
3. GET /users/123/followers → Follower count
4. GET /users/123/following → Following count
5. GET /tracks/456/waveform → Waveform data for each track
... 10+ API calls, lots of unused data

After BFF:

Mobile App:
1. GET /mobile/bff/user-profile/123
   → Returns exactly what mobile needs in ONE call
   {
     name, avatar, trackCount, followerCount,
     recentTracks: [{ title, duration, waveformUrl }]
   }

Main Benefits

BenefitDescription
API AggregationCombine multiple backend calls into single response
Response ShapingTransform data specifically for UI needs
Security LayerHandle auth, hide internal services
CachingReduce load on downstream services
Protocol TranslationConvert gRPC/SOAP to REST/GraphQL
Rate LimitingProtect backend from frontend traffic spikes
ResilienceImplement circuit breakers, retries

Why Java for BFF?

ReasonExplanation
PerformanceJVM is highly optimized for concurrent requests
Type SafetyStrong typing catches errors at compile time
EcosystemSpring Boot, Resilience4j, mature libraries
Team SkillsMany enterprises have Java expertise
ObservabilityExcellent monitoring/tracing tools
Async SupportCompletableFuture, WebFlux for parallel calls

BFF vs API Gateway

AspectAPI GatewayBFF
PurposeRouting, auth, rate limitingData aggregation, transformation
LogicMinimal business logicClient-specific business logic
OwnershipPlatform/Infra teamFrontend team
Per-clientUsually one gatewayOne per client type
ExamplesKong, AWS API GatewayCustom Spring Boot service

Often used together:

Client → API Gateway → BFF → Microservices
         (routing)    (aggregation)

Example Use Case

@RestController
@RequestMapping("/bff/dashboard")
public class DashboardBffController {
    
    @GetMapping
    public DashboardResponse getDashboard(@AuthUser User user) {
        // Aggregate multiple service calls in parallel
        CompletableFuture<UserProfile> profile = userService.getProfile(user.getId());
        CompletableFuture<List<Order>> orders = orderService.getRecent(user.getId());
        CompletableFuture<Notifications> notifications = notificationService.get(user.getId());
        
        // Wait for all and combine for frontend
        CompletableFuture.allOf(profile, orders, notifications).join();
        
        // Shape response specifically for frontend needs
        return DashboardResponse.builder()
            .profile(profile.join())
            .recentOrders(orders.join().stream().limit(5).toList())
            .unreadCount(notifications.join().getUnreadCount())
            .build();
    }
}

Key Insight

BFF moves complexity from the frontend (browser/mobile) to a server where it's easier to optimize, secure, and monitor. It acts as a translation layer between generic microservices and specific frontend needs.


7. Scrum/Agile for Quality

Quality-Focused Scrum Practices

Definition of Done (DoD)

✅ Code reviewed by at least one peer
✅ Unit tests written (>80% coverage)
✅ Integration tests passing
✅ No critical/high SonarQube issues
✅ Documentation updated
✅ Accessibility requirements met
✅ Performance benchmarks passed

Sprint Ceremonies for Quality

CeremonyQuality Focus
Sprint PlanningInclude testing effort in estimates
Daily StandupSurface blockers early
Sprint ReviewDemo working software, get feedback
RetrospectiveIdentify quality improvement areas
Backlog RefinementClarify acceptance criteria

Best Practices

  1. Test-Driven Development (TDD): Write tests before code
  2. Pair Programming: Real-time code review
  3. Continuous Integration: Automated quality gates
  4. Technical Debt Sprints: Allocate capacity for refactoring
  5. Bug Triage: Address critical bugs immediately
  6. Shift-Left Testing: Test early in the development cycle

8. React Re-render Optimization

How React Re-rendering Works

The React Rendering Process

State/Props Change → Render Phase → Reconciliation → Commit Phase → DOM Update
                     (Virtual DOM)   (Diffing)        (Actual DOM)
  1. Render Phase: React calls your component function to get the new Virtual DOM
  2. Reconciliation: React compares (diffs) the new Virtual DOM with the previous one
  3. Commit Phase: React updates only the changed parts in the actual DOM

What Triggers a Re-render?

TriggerDescriptionExample
State ChangeCalling setState or state setter from useStatesetCount(count + 1)
Props ChangeParent passes different props
Parent Re-rendersWhen parent re-renders, all children re-render by defaultParent state changes
Context ChangeWhen context value changes<Context.Provider value={newValue}>
Force UpdateExplicitly forcing update (rare)forceUpdate() in class components
function Parent() {
  const [count, setCount] = useState(0);
  
  // When count changes:
  // 1. Parent re-renders
  // 2. Child re-renders (even if its props didn't change!)
  // 3. GrandChild re-renders
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <Child /> {/* Re-renders even though it has no props! */}
    </div>
  );
}

function Child() {
  console.log('Child rendered'); // Logs on every parent state change
  return <GrandChild />;
}

function GrandChild() {
  console.log('GrandChild rendered'); // Also logs!
  return <div>I'm a grandchild</div>;
}

Shallow Comparison Explained

React uses shallow comparison (also called shallow equality) to determine if props or state have changed.

What is Shallow Comparison?

// Shallow comparison checks:
// 1. For primitives: Are the values equal?
// 2. For objects/arrays: Are the REFERENCES equal? (not the contents!)

// === Primitives (Shallow comparison works as expected) ===
const a = 5;
const b = 5;
a === b; // true ✅ - same value

const str1 = 'hello';
const str2 = 'hello';
str1 === str2; // true ✅ - same value

// === Objects (Shallow comparison compares REFERENCES) ===
const obj1 = { name: 'John' };
const obj2 = { name: 'John' };
obj1 === obj2; // false ❌ - different references (different objects in memory)

const obj3 = obj1;
obj1 === obj3; // true ✅ - same reference (pointing to same object)

// === Arrays (Same behavior as objects) ===
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
arr1 === arr2; // false ❌ - different references

const arr3 = arr1;
arr1 === arr3; // true ✅ - same reference

Why This Matters in React

function Parent() {
  const [count, setCount] = useState(0);
  
  // ❌ Problem: New object created on EVERY render
  const user = { name: 'John', age: 30 };
  
  // ❌ Problem: New array created on EVERY render
  const items = ['apple', 'banana'];
  
  // ❌ Problem: New function created on EVERY render
  const handleClick = () => console.log('clicked');
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {/* Even with React.memo, Child re-renders because props are new objects! */}
      <MemoizedChild user={user} items={items} onClick={handleClick} />
    </div>
  );
}

const MemoizedChild = React.memo(({ user, items, onClick }) => {
  console.log('Child rendered'); // Still logs every time!
  return <div>{user.name}</div>;
});

Shallow Comparison Visualization

Re-render #1:                    Re-render #2:
┌─────────────────┐              ┌─────────────────┐
│ user (ref: 0x1) │              │ user (ref: 0x2) │  ← NEW reference!
│ { name: 'John' }│              │ { name: 'John' }│    Same content, but
└─────────────────┘              └─────────────────┘    React sees it as different

Shallow comparison: 0x1 === 0x2 → false → RE-RENDER!

Common Causes of Unnecessary Re-renders

1. Inline Object/Array Props

// ❌ Bad: New object on every render
<UserCard style={{ color: 'red' }} />
<List items={[1, 2, 3]} />

// ✅ Good: Stable reference
const cardStyle = { color: 'red' };
const numbers = [1, 2, 3];
<UserCard style={cardStyle} />
<List items={numbers} />

// ✅ Better: Define outside component or use useMemo
const CARD_STYLE = { color: 'red' }; // Outside component

function Parent() {
  const items = useMemo(() => [1, 2, 3], []);
  return <List items={items} />;
}

2. Inline Function Props

// ❌ Bad: New function on every render
<Button onClick={() => handleClick(id)} />

// ✅ Good: Stable function reference with useCallback
const handleButtonClick = useCallback(() => {
  handleClick(id);
}, [id]);
<Button onClick={handleButtonClick} />

3. State Too High in Component Tree

// ❌ Bad: Input state in parent causes Header to re-render
function App() {
  const [searchTerm, setSearchTerm] = useState('');
  return (
    <>
      <Header /> {/* Re-renders on every keystroke! */}
      <SearchInput value={searchTerm} onChange={setSearchTerm} />
      <Content />
    </>
  );
}

// ✅ Good: State colocated in the component that uses it
function App() {
  return (
    <>
      <Header /> {/* Never re-renders due to search */}
      <SearchInput /> {/* Manages its own state */}
      <Content />
    </>
  );
}

function SearchInput() {
  const [searchTerm, setSearchTerm] = useState('');
  return <input value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />;
}

Optimization Solutions in Detail

Solution 1: React.memo - Memoize Components

React.memo wraps a component and prevents re-renders if props haven't changed (using shallow comparison).

// Without React.memo - re-renders whenever parent re-renders
function ExpensiveList({ items }) {
  console.log('ExpensiveList rendered');
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

// With React.memo - only re-renders if **items** reference changes
const ExpensiveList = React.memo(function ExpensiveList({ items }) {
  console.log('ExpensiveList rendered');
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
});

// Custom comparison function for deep comparison (use sparingly!)
const ExpensiveList = React.memo(
  function ExpensiveList({ items }) {
    return <ul>{/* ... */}</ul>;
  },
  (prevProps, nextProps) => {
    // Return true if props are equal (skip re-render)
    // Return false if props are different (re-render)
    return prevProps.items.length === nextProps.items.length &&
           prevProps.items.every((item, i) => item.id === nextProps.items[i].id);
  }
);

Solution 2: useMemo - Memoize Expensive Calculations

useMemo caches the result of a calculation and only recalculates when dependencies change.

function ProductList({ products, filterTerm }) {
  // ❌ Bad: Filters and sorts on EVERY render
  const filteredProducts = products
    .filter(p => p.name.includes(filterTerm))
    .sort((a, b) => a.price - b.price);

  // ✅ Good: Only recalculates when products or filterTerm changes
  const filteredProducts = useMemo(() => {
    console.log('Filtering and sorting...'); // Only logs when dependencies change
    return products
      .filter(p => p.name.includes(filterTerm))
      .sort((a, b) => a.price - b.price);
  }, [products, filterTerm]);

  return <ul>{filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
// useMemo for stable object references
function UserProfile({ userId }) {
  const [refreshCount, setRefreshCount] = useState(0);
  
  // ❌ Bad: New object on every render
  const config = { theme: 'dark', userId };
  
  // ✅ Good: Stable reference, only changes when userId changes
  const config = useMemo(() => ({ 
    theme: 'dark', 
    userId 
  }), [userId]);
  
  return (
    <>
      <button onClick={() => setRefreshCount(c => c + 1)}>Refresh</button>
      <MemoizedSettings config={config} /> {/* Won't re-render on refresh */}
    </>
  );
}

Solution 3: useCallback - Memoize Functions

useCallback returns a memoized version of a callback that only changes when dependencies change.

function TodoList({ todos }) {
  const [filter, setFilter] = useState('all');
  
  // ❌ Bad: New function on every render
  const handleDelete = (id) => {
    deleteTodo(id);
  };
  
  // ✅ Good: Stable function reference
  const handleDelete = useCallback((id) => {
    deleteTodo(id);
  }, []); // Empty deps = function never changes
  
  // ✅ With dependencies
  const handleToggle = useCallback((id) => {
    toggleTodo(id, filter);
  }, [filter]); // Function updates when filter changes
  
  return (
    <ul>
      {todos.map(todo => (
        <MemoizedTodoItem 
          key={todo.id}
          todo={todo}
          onDelete={handleDelete}  // Stable reference
          onToggle={handleToggle}
        />
      ))}
    </ul>
  );
}

const MemoizedTodoItem = React.memo(function TodoItem({ todo, onDelete, onToggle }) {
  console.log(`Rendering todo: ${todo.id}`);
  return (
    <li>
      {todo.text}
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
});

Solution 4: State Colocation

Move state as close as possible to where it's used.

// ❌ Bad: Global state causes entire app to re-render
function App() {
  const [modalOpen, setModalOpen] = useState(false);
  const [searchTerm, setSearchTerm] = useState('');
  const [selectedTab, setSelectedTab] = useState('home');
  
  return (
    <div>
      <Header /> {/* Re-renders on searchTerm change */}
      <Tabs selected={selectedTab} onChange={setSelectedTab} />
      <SearchBar value={searchTerm} onChange={setSearchTerm} />
      <Content />
      {modalOpen && <Modal onClose={() => setModalOpen(false)} />}
    </div>
  );
}

// ✅ Good: State colocated to relevant components
function App() {
  return (
    <div>
      <Header />
      <TabsWithState /> {/* Manages its own selectedTab state */}
      <SearchBarWithState /> {/* Manages its own searchTerm state */}
      <Content />
      <ModalTrigger /> {/* Manages its own modalOpen state */}
    </div>
  );
}

function SearchBarWithState() {
  const [searchTerm, setSearchTerm] = useState('');
  return <input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />;
}

Solution 5: Virtualization for Long Lists

Render only visible items instead of the entire list.

import { FixedSizeList } from 'react-window';

// ❌ Bad: Renders all 10,000 items
function BadList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

// ✅ Good: Only renders visible items (~10-20 at a time)
function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={400}      // Viewport height
      itemCount={items.length}
      itemSize={35}     // Row height
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

Debugging Re-renders

Using React DevTools Profiler

  1. Open React DevTools → Profiler tab
  2. Click "Record" and interact with your app
  3. Stop recording and analyze:
    • Which components re-rendered
    • Why they re-rendered
    • How long each render took

Adding Console Logs

function MyComponent({ data }) {
  console.log('MyComponent rendered', { data });
  
  useEffect(() => {
    console.log('data changed:', data);
  }, [data]);
  
  return <div>{/* ... */}</div>;
}

Using why-did-you-render Library

// Setup (development only)
import React from 'react';
if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, { trackAllPureComponents: true });
}

// Mark component to track
MyComponent.whyDidYouRender = true;

Summary: When to Use Each Optimization

ProblemSolution
Child re-renders when parent re-renders (but child props unchanged)React.memo
Expensive calculation runs on every renderuseMemo
Function prop causes child to re-renderuseCallback
State change in one area affects unrelated componentsState Colocation
Rendering thousands of list itemsVirtualization (react-window)
Object/array prop always triggers re-renderuseMemo to stabilize reference

Golden Rule: Don't optimize prematurely. Use React DevTools Profiler to identify actual bottlenecks first!


Additional Real-World Examples

Example 1: E-commerce Product Card with Multiple Optimizations

// ❌ Before: Every parent re-render causes all ProductCards to re-render
function ProductGrid({ products, cart, onAddToCart }) {
  const [sortBy, setSortBy] = useState('price');
  
  // Problem 1: Sorting on every render
  const sortedProducts = products.sort((a, b) => {
    if (sortBy === 'price') return a.price - b.price;
    return a.name.localeCompare(b.name);
  });
  
  return (
    <div>
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="price">Price</option>
        <option value="name">Name</option>
      </select>
      
      {sortedProducts.map(product => (
        <ProductCard
          key={product.id}
          product={product}
          // Problem 2: New object every render
          cartInfo={{ 
            inCart: cart.includes(product.id),
            quantity: cart.filter(id => id === product.id).length
          }}
          // Problem 3: New function every render
          onAdd={() => onAddToCart(product.id)}
        />
      ))}
    </div>
  );
}

// ✅ After: Optimized with useMemo, useCallback, and React.memo
function ProductGrid({ products, cart, onAddToCart }) {
  const [sortBy, setSortBy] = useState('price');
  
  // Solution 1: Memoize sorted products
  const sortedProducts = useMemo(() => {
    console.log('Sorting products...');
    return [...products].sort((a, b) => {
      if (sortBy === 'price') return a.price - b.price;
      return a.name.localeCompare(b.name);
    });
  }, [products, sortBy]);
  
  // Solution 3: Memoize the add to cart handler
  const handleAddToCart = useCallback((productId) => {
    onAddToCart(productId);
  }, [onAddToCart]);
  
  return (
    <div>
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="price">Price</option>
        <option value="name">Name</option>
      </select>
      
      {sortedProducts.map(product => (
        <MemoizedProductCard
          key={product.id}
          product={product}
          // Solution 2: Pass primitives instead of objects when possible
          inCart={cart.includes(product.id)}
          quantity={cart.filter(id => id === product.id).length}
          onAdd={handleAddToCart}
        />
      ))}
    </div>
  );
}

// Memoized ProductCard - only re-renders when its specific props change
const MemoizedProductCard = React.memo(function ProductCard({ 
  product, 
  inCart, 
  quantity, 
  onAdd 
}) {
  console.log(`Rendering ProductCard: ${product.name}`);
  
  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={() => onAdd(product.id)}>
        {inCart ? `In Cart (${quantity})` : 'Add to Cart'}
      </button>
    </div>
  );
});

Example 2: Form with Debounced Search and Isolated State

// ❌ Before: Every keystroke re-renders the entire form
function SearchPage() {
  const [searchTerm, setSearchTerm] = useState('');
  const [filters, setFilters] = useState({ category: 'all', inStock: true });
  const [results, setResults] = useState([]);
  
  // This runs on every keystroke!
  useEffect(() => {
    fetchResults(searchTerm, filters).then(setResults);
  }, [searchTerm, filters]);
  
  return (
    <div>
      <Header /> {/* Re-renders on every keystroke! */}
      <Sidebar /> {/* Re-renders on every keystroke! */}
      
      <input 
        value={searchTerm} 
        onChange={e => setSearchTerm(e.target.value)} 
      />
      
      <FilterPanel filters={filters} onChange={setFilters} />
      <ResultsList results={results} />
    </div>
  );
}

// ✅ After: Isolated search input with debouncing
function SearchPage() {
  const [filters, setFilters] = useState({ category: 'all', inStock: true });
  const [results, setResults] = useState([]);
  
  // Search term handled in isolated component
  const handleSearch = useCallback((term) => {
    fetchResults(term, filters).then(setResults);
  }, [filters]);
  
  return (
    <div>
      <Header /> {/* Never re-renders due to search */}
      <Sidebar /> {/* Never re-renders due to search */}
      
      <DebouncedSearchInput onSearch={handleSearch} />
      
      <MemoizedFilterPanel filters={filters} onChange={setFilters} />
      <MemoizedResultsList results={results} />
    </div>
  );
}

// Isolated search input - manages its own state
function DebouncedSearchInput({ onSearch }) {
  const [inputValue, setInputValue] = useState('');
  
  // Debounce the search callback
  const debouncedSearch = useMemo(
    () => debounce((term) => onSearch(term), 300),
    [onSearch]
  );
  
  const handleChange = (e) => {
    const value = e.target.value;
    setInputValue(value);
    debouncedSearch(value);
  };
  
  return (
    <input 
      value={inputValue} 
      onChange={handleChange}
      placeholder="Search..."
    />
  );
}

const MemoizedFilterPanel = React.memo(FilterPanel);
const MemoizedResultsList = React.memo(ResultsList);

Example 3: Context Optimization to Prevent Mass Re-renders

// ❌ Before: Every context update re-renders ALL consumers
const AppContext = React.createContext();

function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [notifications, setNotifications] = useState([]);
  
  // One big context - any change re-renders everything
  const value = {
    user, setUser,
    theme, setTheme,
    notifications, setNotifications
  };
  
  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
}

// Problem: Header only needs theme, but re-renders when notifications change!
function Header() {
  const { theme } = useContext(AppContext);
  console.log('Header rendered'); // Logs when ANY context value changes
  return <header className={theme}>...</header>;
}

// ✅ After: Split contexts by update frequency
const UserContext = React.createContext();
const ThemeContext = React.createContext();
const NotificationContext = React.createContext();

function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [notifications, setNotifications] = useState([]);
  
  // Memoize context values to prevent unnecessary re-renders
  const userValue = useMemo(() => ({ user, setUser }), [user]);
  const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
  const notificationValue = useMemo(
    () => ({ notifications, setNotifications }), 
    [notifications]
  );
  
  return (
    <UserContext.Provider value={userValue}>
      <ThemeContext.Provider value={themeValue}>
        <NotificationContext.Provider value={notificationValue}>
          {children}
        </NotificationContext.Provider>
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

// Now Header only re-renders when theme changes
function Header() {
  const { theme } = useContext(ThemeContext);
  console.log('Header rendered'); // Only logs when theme changes
  return <header className={theme}>...</header>;
}

Example 4: Table with Cell-Level Optimization

// ❌ Before: Editing one cell re-renders entire table
function DataTable({ data, onCellUpdate }) {
  return (
    <table>
      <tbody>
        {data.map(row => (
          <tr key={row.id}>
            {Object.entries(row).map(([key, value]) => (
              <td key={key}>
                <input
                  value={value}
                  // New function for every cell on every render!
                  onChange={e => onCellUpdate(row.id, key, e.target.value)}
                />
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

// ✅ After: Cell-level isolation with memoization
function DataTable({ data, onCellUpdate }) {
  return (
    <table>
      <tbody>
        {data.map(row => (
          <MemoizedTableRow 
            key={row.id} 
            row={row} 
            onCellUpdate={onCellUpdate}
          />
        ))}
      </tbody>
    </table>
  );
}

const MemoizedTableRow = React.memo(function TableRow({ row, onCellUpdate }) {
  console.log(`Rendering row: ${row.id}`);
  
  return (
    <tr>
      {Object.entries(row).map(([key, value]) => (
        <MemoizedTableCell
          key={key}
          rowId={row.id}
          cellKey={key}
          value={value}
          onUpdate={onCellUpdate}
        />
      ))}
    </tr>
  );
});

const MemoizedTableCell = React.memo(function TableCell({ 
  rowId, 
  cellKey, 
  value, 
  onUpdate 
}) {
  console.log(`Rendering cell: ${rowId}-${cellKey}`);
  
  // Local state for immediate feedback
  const [localValue, setLocalValue] = useState(value);
  
  // Sync with parent when prop changes
  useEffect(() => {
    setLocalValue(value);
  }, [value]);
  
  // Debounce updates to parent
  const debouncedUpdate = useMemo(
    () => debounce((val) => onUpdate(rowId, cellKey, val), 300),
    [rowId, cellKey, onUpdate]
  );
  
  const handleChange = (e) => {
    const newValue = e.target.value;
    setLocalValue(newValue);      // Immediate local update
    debouncedUpdate(newValue);    // Debounced parent update
  };
  
  return (
    <td>
      <input value={localValue} onChange={handleChange} />
    </td>
  );
});

Example 5: Expensive Component with Lazy Rendering

// ❌ Before: Heavy chart renders on every parent update
function Dashboard({ userData, chartData, settings }) {
  return (
    <div>
      <UserStats data={userData} />
      {/* This expensive chart re-renders whenever userData or settings change */}
      <HeavyChart data={chartData} />
      <SettingsPanel settings={settings} />
    </div>
  );
}

// ✅ After: Isolated expensive component
function Dashboard({ userData, chartData, settings }) {
  return (
    <div>
      <UserStats data={userData} />
      {/* Chart is memoized and only re-renders when chartData changes */}
      <MemoizedHeavyChart data={chartData} />
      <SettingsPanel settings={settings} />
    </div>
  );
}

const MemoizedHeavyChart = React.memo(function HeavyChart({ data }) {
  console.log('HeavyChart rendering...'); // Only when data changes
  
  // Expensive calculation
  const processedData = useMemo(() => {
    console.log('Processing chart data...');
    return data.map(point => ({
      ...point,
      normalized: point.value / Math.max(...data.map(d => d.value)),
      movingAverage: calculateMovingAverage(data, point.index)
    }));
  }, [data]);
  
  return (
    <div className="chart">
      {/* Render complex chart */}
    </div>
  );
});

Example 6: Optimizing Children Prop Pattern

// ❌ Before: Wrapper re-renders children on every state change
function ExpandableSection({ title, children }) {
  const [isExpanded, setIsExpanded] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsExpanded(!isExpanded)}>
        {title} {isExpanded ? '▼' : '▶'}
      </button>
      {isExpanded && (
        <div className="content">
          {children} {/* Children re-render when isExpanded changes */}
        </div>
      )}
    </div>
  );
}

// Usage - ExpensiveContent re-renders on every toggle!
<ExpandableSection title="Details">
  <ExpensiveContent data={data} />
</ExpandableSection>

// ✅ After: Children prop is already memoized by React!
// The issue is usually in HOW you pass children

// Solution 1: Children as prop (already optimized by React)
// React memoizes children passed as JSX
function Parent() {
  return (
    <ExpandableSection title="Details">
      <ExpensiveContent data={data} /> {/* This is stable! */}
    </ExpandableSection>
  );
}

// Solution 2: Render prop with useCallback
function ExpandableSection({ title, renderContent }) {
  const [isExpanded, setIsExpanded] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsExpanded(!isExpanded)}>
        {title} {isExpanded ? '▼' : '▶'}
      </button>
      {isExpanded && (
        <div className="content">
          {renderContent()}
        </div>
      )}
    </div>
  );
}

// Usage with stable render function
function Parent() {
  const renderContent = useCallback(() => (
    <ExpensiveContent data={data} />
  ), [data]);
  
  return (
    <ExpandableSection 
      title="Details" 
      renderContent={renderContent}
    />
  );
}

Performance Checklist

Before optimizing, ask yourself:

  • Is there actually a performance problem? (Measure first!)
  • Did I check React DevTools Profiler?
  • Can I colocate state closer to where it's used?
  • Am I creating new objects/arrays/functions in render?
  • Are my context values memoized?
  • Do I need all this data at this level of the tree?
  • Would splitting the component help?
  • For lists: Am I using stable keys? Should I virtualize?

9. Jest vs Selenium/E2E Testing

Comparison Table

AspectJest (Unit/Component)Selenium/Playwright (E2E)
ScopeIndividual units/componentsFull user journeys
SpeedFast (ms per test)Slow (seconds per test)
Environmentjsdom (simulated)Real browser
FlakinessLowHigher
CostLowHigh (infrastructure)
Feedback LoopImmediateDelayed
Coverage Goal70-80% of tests10-20% of tests

When to Use Each

Jest + React Testing Library

  • Unit testing pure functions
  • Component behavior testing
  • Hook testing
  • Integration between components
  • Fast CI feedback
// Component test example
test('submits form with valid data', async () => {
  render(<LoginForm onSubmit={mockSubmit} />);
  
  await userEvent.type(screen.getByLabelText('Email'), 'test@example.com');
  await userEvent.type(screen.getByLabelText('Password'), 'password123');
  await userEvent.click(screen.getByRole('button', { name: /submit/i }));
  
  expect(mockSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123'
  });
});

Selenium/Playwright/Cypress

  • Critical user paths (login, checkout)
  • Cross-browser compatibility
  • Visual regression testing
  • Full integration with real APIs
  • Performance testing
// E2E test example (Playwright)
test('complete purchase flow', async ({ page }) => {
  await page.goto('/products');
  await page.click('[data-testid="product-1"]');
  await page.click('button:has-text("Add to Cart")');
  await page.click('[data-testid="checkout"]');
  await page.fill('#card-number', '4242424242424242');
  await page.click('button:has-text("Pay")');
  await expect(page.locator('.confirmation')).toBeVisible();
});

10. GraphQL vs REST

When to Choose GraphQL

ScenarioRecommendation
Multiple clients with different data needsGraphQL
Deeply nested/related dataGraphQL
Rapid frontend iterationGraphQL
Reducing over-fetching/under-fetchingGraphQL
Real-time subscriptionsGraphQL

When to Choose REST

ScenarioRecommendation
Simple CRUD operationsREST
File uploads/downloadsREST
Caching requirements (HTTP caching)REST
Team familiarityREST
Public APIsREST

Microservices Considerations

GraphQL Gateway Pattern:
┌─────────┐     ┌──────────────────┐     ┌─────────────────┐
│ Client  │────▶│ GraphQL Gateway  │────▶│ Microservice A  │
└─────────┘     │ (Federation)     │────▶│ Microservice B  │
                └──────────────────┘────▶│ Microservice C  │
                                         └─────────────────┘

Benefits in Microservices:

  • Schema stitching/federation for unified API
  • Client-driven queries across services
  • Strong typing with schema

Challenges:

  • N+1 query problems
  • Caching complexity
  • Rate limiting complexity

11. Microservice Design Characteristics

Technical Characteristics

CharacteristicDescription
Single ResponsibilityOne business capability per service
Independently DeployableDeploy without affecting other services
Loosely CoupledMinimal dependencies on other services
Highly CohesiveRelated functionality grouped together
Owns Its DataDedicated database per service
API-FirstWell-defined contracts (OpenAPI/AsyncAPI)
ObservableLogging, metrics, tracing built-in
ResilientHandles failures gracefully
StatelessNo local session state
ContainerizedRuns in Docker/Kubernetes

Design Principles

┌─────────────────────────────────────────────────────────────┐
│                    API Gateway / BFF                        │
└─────────────────────────────────────────────────────────────┘
         │              │              │              │
    ┌────▼────┐    ┌────▼────┐   ┌────▼────┐   ┌────▼────┐
    │ User    │    │ Order   │   │ Payment │   │ Notif   │
    │ Service │    │ Service │   │ Service │   │ Service │
    └────┬────┘    └────┬────┘   └────┬────┘   └────┬────┘
         │              │              │              │
    ┌────▼────┐    ┌────▼────┐   ┌────▼────┐   ┌────▼────┐
    │ User DB │    │ Order DB│   │ Pay DB  │   │ Notif DB│
    └─────────┘    └─────────┘   └─────────┘   └─────────┘

Key Practices

  • Domain-Driven Design: Bounded contexts define service boundaries
  • Event-Driven Communication: Async messaging for loose coupling
  • Health Checks: Liveness and readiness probes
  • Configuration Externalization: Environment-specific configs
  • Graceful Shutdown: Handle in-flight requests

12. Test Coverage and Mocking

Coverage Strategy

Test Pyramid:
           ┌───────┐
           │  E2E  │  10% - Critical paths
          ┌┴───────┴┐
          │Integration│  20% - Service boundaries
         ┌┴──────────┴┐
         │    Unit     │  70% - Business logic
         └─────────────┘

Unit Test Coverage Goals

Code TypeTarget Coverage
Business Logic90%+
Utilities/Helpers90%+
React Components80%+
API Integration70%+
Configuration50%+

Mocking Strategies

1. Function/Module Mocking (Jest)

// Mock entire module
jest.mock('./userService', () => ({
  getUser: jest.fn().mockResolvedValue({ id: 1, name: 'Test' })
}));

// Mock specific implementation
const mockFetch = jest.spyOn(global, 'fetch').mockResolvedValue({
  json: () => Promise.resolve({ data: 'mocked' })
});

2. API Mocking (MSW - Mock Service Worker)

import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('/api/users', (req, res, ctx) => {
    return res(ctx.json([{ id: 1, name: 'John' }]));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

3. Component Mocking

jest.mock('./HeavyComponent', () => {
  return function MockedComponent(props) {
    return <div data-testid="mocked-heavy">{props.title}</div>;
  };
});

Coverage Reporting

# Generate coverage report
npm test -- --coverage --coverageReporters=text --coverageReporters=lcov

# Enforce thresholds
# package.json
"jest": {
  "coverageThreshold": {
    "global": {
      "branches": 80,
      "functions": 80,
      "lines": 80,
      "statements": 80
    }
  }
}

13. CI/CD Pipeline Standard

Complete Pipeline Overview

┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│ Commit  │───▶│  Build  │───▶│  Test   │───▶│ Deploy  │───▶│ Monitor │
│         │    │         │    │         │    │ Staging │    │         │
└─────────┘    └─────────┘    └─────────┘    └────┬────┘    └─────────┘
                                                  │
                                             ┌────▼────┐
                                             │  Manual │
                                             │ Approval│
                                             └────┬────┘
                                                  │
                                             ┌────▼────┐
                                             │  Prod   │
                                             └─────────┘

Detailed Pipeline Stages

# Frontend Pipeline
stages:
  - name: Build
    steps:
      - checkout
      - npm ci
      - npm run lint
      - npm run type-check
      - npm run build
      - upload-artifact: dist/

  - name: Test
    steps:
      - npm run test:unit -- --coverage
      - npm run test:integration
      - upload-coverage: coverage/
      - sonarqube-scan

  - name: Security
    steps:
      - npm audit --audit-level=high
      - snyk test
      - OWASP dependency check

  - name: Deploy Staging
    steps:
      - download-artifact: dist/
      - deploy-to-s3: staging-bucket
      - invalidate-cloudfront
      - run-e2e-tests

  - name: Deploy Production
    needs: manual-approval
    steps:
      - deploy-to-s3: prod-bucket
      - invalidate-cloudfront
      - smoke-tests
      - notify-team
# Backend Pipeline
stages:
  - name: Build
    steps:
      - checkout
      - mvn clean compile
      - mvn package -DskipTests
      - docker build -t app:$VERSION .

  - name: Test
    steps:
      - mvn test
      - mvn verify (integration tests)
      - jacoco-coverage-report
      - sonarqube-scan

  - name: Security
    steps:
      - OWASP dependency-check
      - trivy scan (container)
      - secrets scan

  - name: Deploy
    steps:
      - push-to-ecr
      - update-ecs-task
      - health-check
      - rollback-on-failure

14. AWS Deployment for React & Java

React Frontend Deployment

┌─────────────────────────────────────────────────────────────┐
│                     CloudFront (CDN)                        │
│                  - SSL/TLS termination                      │
│                  - Edge caching                             │
│                  - Custom domain                            │
└──────────────────────────┬──────────────────────────────────┘
                           │
               ┌───────────▼───────────┐
               │      S3 Bucket        │
               │  - Static hosting     │
               │  - SPA config         │
               │  - Versioned builds   │
               └───────────────────────┘

Architecture Components

Frontend:
  - S3: Static file hosting
  - CloudFront: CDN, caching, HTTPS
  - Route 53: DNS management
  - Certificate Manager: SSL certificates
  - WAF: Web Application Firewall

Backend:
  - ECS Fargate: Container orchestration (or EKS)
  - ALB: Load balancing, health checks
  - ECR: Container registry
  - RDS/Aurora: Database
  - ElastiCache: Redis for caching
  - Secrets Manager: Credentials

Java Backend Deployment

┌─────────────────────────────────────────────────────────────┐
│                    Application Load Balancer                │
└──────────────────────────┬──────────────────────────────────┘
                           │
          ┌────────────────┼────────────────┐
          │                │                │
     ┌────▼────┐      ┌────▼────┐      ┌────▼────┐
     │  ECS    │      │  ECS    │      │  ECS    │
     │ Task 1  │      │ Task 2  │      │ Task 3  │
     └────┬────┘      └────┬────┘      └────┬────┘
          │                │                │
          └────────────────┼────────────────┘
                           │
               ┌───────────▼───────────┐
               │    Aurora PostgreSQL  │
               │    (Multi-AZ)         │
               └───────────────────────┘

Terraform Example

# S3 + CloudFront for React
resource "aws_s3_bucket" "frontend" {
  bucket = "my-app-frontend"
}

resource "aws_cloudfront_distribution" "frontend" {
  origin {
    domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
    origin_id   = "S3Origin"
    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
    }
  }
  # ... additional config
}

# ECS for Java Backend
resource "aws_ecs_service" "backend" {
  name            = "backend-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.backend.arn
  desired_count   = 3
  launch_type     = "FARGATE"
  
  load_balancer {
    target_group_arn = aws_lb_target_group.backend.arn
    container_name   = "backend"
    container_port   = 8080
  }
}

15. Cloud-First Banking Architecture

AWS Services Selection

LayerServicePurpose
CDNCloudFrontStatic content, edge caching
API GatewayAPI GatewayRate limiting, authentication
ComputeEKS / ECS FargateContainer orchestration
DatabaseAurora PostgreSQLACID transactions, Multi-AZ
CacheElastiCache RedisSession, frequently accessed data
QueueSQS / EventBridgeAsync processing
StorageS3Documents, backups
SecurityWAF, Shield, KMSProtection, encryption

Auto-Scaling Configuration

# ECS Service Auto Scaling
AutoScaling:
  min_capacity: 3
  max_capacity: 50
  
  policies:
    - type: TargetTracking
      metric: ECSServiceAverageCPUUtilization
      target: 70
      
    - type: TargetTracking
      metric: ECSServiceAverageMemoryUtilization
      target: 75
      
    - type: StepScaling
      metric: ALBRequestCountPerTarget
      thresholds:
        - lower: 1000
          adjustment: +2
        - lower: 5000
          adjustment: +5

# Aurora Auto Scaling (Read Replicas)
AuroraAutoScaling:
  min_replicas: 2
  max_replicas: 15
  target_cpu: 70

Observability Stack

┌─────────────────────────────────────────────────────────────┐
│                    Observability Platform                    │
├──────────────────┬──────────────────┬───────────────────────┤
│     Logging      │     Metrics      │      Tracing          │
├──────────────────┼──────────────────┼───────────────────────┤
│ CloudWatch Logs  │ CloudWatch       │ X-Ray / OpenTelemetry │
│ + Log Insights   │ + Custom Metrics │                       │
├──────────────────┼──────────────────┼───────────────────────┤
│ OpenSearch       │ Prometheus       │ Jaeger                │
│ (ELK alternative)│ + Grafana        │                       │
└──────────────────┴──────────────────┴───────────────────────┘

Alerting: CloudWatch Alarms → SNS → PagerDuty/Slack
Dashboards: Grafana / CloudWatch Dashboards
APM: X-Ray Service Map

Key Banking Requirements

  • Multi-AZ Deployment: High availability
  • Encryption: At rest (KMS) and in transit (TLS 1.3)
  • Audit Logging: CloudTrail for compliance
  • DR Strategy: Cross-region replication, RTO < 4hrs, RPO < 1hr
  • Network Isolation: VPC, private subnets, security groups

16. Resilience Patterns in Java BFF

Implementation with Resilience4j

1. Circuit Breaker

@Service
public class PaymentService {
    
    @CircuitBreaker(name = "paymentService", fallbackMethod = "fallbackPayment")
    public PaymentResponse processPayment(PaymentRequest request) {
        return paymentClient.process(request);
    }
    
    private PaymentResponse fallbackPayment(PaymentRequest request, Exception e) {
        log.warn("Circuit breaker triggered for payment: {}", e.getMessage());
        return PaymentResponse.builder()
            .status(PaymentStatus.PENDING)
            .message("Payment queued for processing")
            .build();
    }
}

// application.yml
resilience4j:
  circuitbreaker:
    instances:
      paymentService:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 30s
        permittedNumberOfCallsInHalfOpenState: 5

2. Retry with Exponential Backoff

@Retry(name = "externalApi", fallbackMethod = "fallbackResponse")
public Response callExternalApi(Request request) {
    return externalClient.call(request);
}

// application.yml
resilience4j:
  retry:
    instances:
      externalApi:
        maxAttempts: 3
        waitDuration: 1s
        exponentialBackoffMultiplier: 2
        retryExceptions:
          - java.io.IOException
          - java.net.SocketTimeoutException
        ignoreExceptions:
          - com.example.BusinessException

3. Bulkhead (Isolation)

@Bulkhead(name = "inventoryService", type = Bulkhead.Type.THREADPOOL)
public InventoryResponse checkInventory(String productId) {
    return inventoryClient.check(productId);
}

// application.yml
resilience4j:
  bulkhead:
    instances:
      inventoryService:
        maxConcurrentCalls: 20
        maxWaitDuration: 500ms
  thread-pool-bulkhead:
    instances:
      inventoryService:
        maxThreadPoolSize: 10
        coreThreadPoolSize: 5
        queueCapacity: 100

4. Timeouts

@TimeLimiter(name = "slowService")
public CompletableFuture<Response> callSlowService() {
    return CompletableFuture.supplyAsync(() -> slowClient.getData());
}

// application.yml
resilience4j:
  timelimiter:
    instances:
      slowService:
        timeoutDuration: 3s
        cancelRunningFuture: true

Combined Pattern

@CircuitBreaker(name = "orderService")
@Retry(name = "orderService")
@Bulkhead(name = "orderService")
@TimeLimiter(name = "orderService")
public CompletableFuture<Order> createOrder(OrderRequest request) {
    return CompletableFuture.supplyAsync(() -> orderClient.create(request));
}

17. API Security Best Practices

OAuth2/OIDC Implementation

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            );
        return http.build();
    }
    
    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
        converter.setAuthoritiesClaimName("roles");
        converter.setAuthorityPrefix("ROLE_");
        
        JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
        return jwtConverter;
    }
}

JWT Validation

@Component
public class JwtTokenValidator {
    
    public void validateToken(String token) {
        Jwt jwt = jwtDecoder.decode(token);
        
        // Validate issuer
        if (!validIssuers.contains(jwt.getIssuer())) {
            throw new InvalidTokenException("Invalid issuer");
        }
        
        // Validate audience
        if (!jwt.getAudience().contains(expectedAudience)) {
            throw new InvalidTokenException("Invalid audience");
        }
        
        // Validate expiration
        if (jwt.getExpiresAt().isBefore(Instant.now())) {
            throw new TokenExpiredException("Token expired");
        }
    }
}

Rate Limiting (API Gateway / Application)

@Configuration
public class RateLimitConfig {
    
    @Bean
    public RateLimiter rateLimiter() {
        return RateLimiter.of("api", RateLimiterConfig.custom()
            .limitForPeriod(100)
            .limitRefreshPeriod(Duration.ofSeconds(1))
            .timeoutDuration(Duration.ofMillis(500))
            .build());
    }
}

// AWS API Gateway Throttling
{
  "throttle": {
    "rateLimit": 1000,
    "burstLimit": 2000
  }
}

Input Validation

@RestController
@Validated
public class UserController {
    
    @PostMapping("/users")
    public User createUser(@Valid @RequestBody CreateUserRequest request) {
        return userService.create(request);
    }
}

public record CreateUserRequest(
    @NotBlank @Size(max = 100) String name,
    @Email @NotBlank String email,
    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$") String phone,
    @Min(18) @Max(120) Integer age
) {}

Secrets Management (AWS)

@Configuration
public class SecretsConfig {
    
    @Bean
    public DataSource dataSource(SecretsManagerClient secretsClient) {
        String secretJson = secretsClient.getSecretValue(
            GetSecretValueRequest.builder()
                .secretId("prod/database/credentials")
                .build()
        ).secretString();
        
        DbCredentials creds = objectMapper.readValue(secretJson, DbCredentials.class);
        
        return DataSourceBuilder.create()
            .url(creds.url())
            .username(creds.username())
            .password(creds.password())
            .build();
    }
}

18. Distributed Transactions & Saga

The Problem: Why Traditional Transactions Don't Work in Microservices

In a monolithic application, you can use ACID transactions:

BEGIN TRANSACTION;
  INSERT INTO orders (id, user_id, total) VALUES (1, 100, 500);
  UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 'ABC';
  INSERT INTO payments (order_id, amount, status) VALUES (1, 500, 'SUCCESS');
COMMIT;
-- If anything fails, ROLLBACK undoes everything

But in microservices:

  • Each service has its own database (database per service pattern)
  • No single database to coordinate transactions
  • Network calls can fail, timeout, or return partial results
  • Services may be written in different languages/technologies
❌ Cannot do this across services:
BEGIN DISTRIBUTED_TRANSACTION;
  OrderService.createOrder();      -- Different DB
  InventoryService.reserve();      -- Different DB  
  PaymentService.charge();         -- Different DB
COMMIT;

Solutions for Distributed Transactions

SolutionDescriptionBest For
Saga PatternSequence of local transactions with compensationsLong-running business processes
Two-Phase Commit (2PC)Coordinator locks all resources, then commitsLegacy systems, tight coupling OK
Outbox PatternStore events in same DB, publish asynchronouslyEvent-driven architectures
Event SourcingStore state as sequence of eventsAudit-heavy, complex domains
TCC (Try-Confirm-Cancel)Reserve resources, then confirm or cancelFinancial transactions

Saga Pattern Deep Dive

The Saga pattern breaks a distributed transaction into a sequence of local transactions, each with a compensating action to undo changes if something fails.

Two Types of Saga

1. Choreography-based Saga (Event-Driven)

Services communicate through events. No central coordinator.

┌─────────────┐   OrderCreated   ┌─────────────┐   PaymentSuccess   ┌─────────────┐
│   Order     │ ───────────────▶ │   Payment   │ ─────────────────▶ │  Inventory  │
│   Service   │                  │   Service   │                    │   Service   │
└──────┬──────┘                  └──────┬──────┘                    └──────┬──────┘
       │                                │                                  │
       │                                │                                  │
       │  PaymentFailed                 │  InventoryFailed                 │
       │ ◀──────────────────────────────│◀─────────────────────────────────│
       │                                │
       ▼                                ▼
  Cancel Order                    Refund Payment
  (Compensation)                  (Compensation)

Pros:

  • Loose coupling
  • Simple for straightforward flows
  • No single point of failure

Cons:

  • Hard to track overall transaction state
  • Complex error handling
  • Difficult to debug

Implementation Example:

// Order Service - Publishes event after local transaction
@Service
public class OrderService {
    
    @Transactional
    public Order createOrder(OrderRequest request) {
        Order order = orderRepository.save(new Order(request));
        
        // Publish event (after commit via TransactionalEventListener)
        eventPublisher.publish(new OrderCreatedEvent(order));
        
        return order;
    }
    
    // Listen for failure events to compensate
    @EventListener
    public void handlePaymentFailed(PaymentFailedEvent event) {
        orderRepository.updateStatus(event.getOrderId(), OrderStatus.CANCELLED);
    }
}

// Payment Service - Listens and reacts
@Service
public class PaymentService {
    
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        try {
            Payment payment = processPayment(event.getOrder());
            eventPublisher.publish(new PaymentSuccessEvent(payment));
        } catch (PaymentException e) {
            eventPublisher.publish(new PaymentFailedEvent(event.getOrderId(), e.getMessage()));
        }
    }
}
2. Orchestration-based Saga (Central Coordinator)

A central Saga Orchestrator controls the entire flow.

                         ┌─────────────────────────┐
                         │    Saga Orchestrator    │
                         │  (Order Saga Service)   │
                         └───────────┬─────────────┘
                                     │
        ┌────────────────────────────┼────────────────────────────┐
        │                            │                            │
        ▼                            ▼                            ▼
   ┌─────────┐                 ┌─────────┐                 ┌─────────┐
   │  Order  │                 │ Payment │                 │Inventory│
   │ Service │                 │ Service │                 │ Service │
   └─────────┘                 └─────────┘                 └─────────┘
   
   Step 1: Create Order ──────────────────────────────────────────────▶
   Step 2: Process Payment ───────────────────────────────────────────▶
   Step 3: Reserve Inventory ─────────────────────────────────────────▶
   
   On Failure: Execute compensating transactions in reverse order

Pros:

  • Easy to understand and debug
  • Centralized error handling
  • Clear transaction state

Cons:

  • Single point of failure (mitigate with HA)
  • Orchestrator can become complex
  • Tighter coupling to orchestrator

Implementation Example:

@Service
@Slf4j
public class OrderSagaOrchestrator {
    
    private final OrderService orderService;
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    private final ShippingService shippingService;
    
    @Transactional
    public SagaResult executeOrderSaga(OrderRequest request) {
        SagaContext context = new SagaContext();
        SagaState state = SagaState.STARTED;
        
        try {
            // Step 1: Create Order
            log.info("Saga Step 1: Creating order");
            Order order = orderService.create(request);
            context.set("orderId", order.getId());
            context.addCompensation("cancelOrder", () -> {
                log.info("Compensating: Cancelling order {}", order.getId());
                orderService.cancel(order.getId());
            });
            state = SagaState.ORDER_CREATED;
            
            // Step 2: Reserve Inventory
            log.info("Saga Step 2: Reserving inventory");
            ReservationResult reservation = inventoryService.reserve(order.getItems());
            context.set("reservationId", reservation.getId());
            context.addCompensation("releaseInventory", () -> {
                log.info("Compensating: Releasing inventory {}", reservation.getId());
                inventoryService.release(reservation.getId());
            });
            state = SagaState.INVENTORY_RESERVED;
            
            // Step 3: Process Payment
            log.info("Saga Step 3: Processing payment");
            PaymentResult payment = paymentService.charge(order.getPaymentInfo());
            context.set("paymentId", payment.getId());
            context.addCompensation("refundPayment", () -> {
                log.info("Compensating: Refunding payment {}", payment.getId());
                paymentService.refund(payment.getId());
            });
            state = SagaState.PAYMENT_PROCESSED;
            
            // Step 4: Initiate Shipping
            log.info("Saga Step 4: Initiating shipping");
            ShippingResult shipping = shippingService.initiate(order);
            state = SagaState.COMPLETED;
            
            log.info("Saga completed successfully for order {}", order.getId());
            return SagaResult.success(order, payment, shipping);
            
        } catch (Exception e) {
            log.error("Saga failed at state {}: {}", state, e.getMessage());
            
            // Execute compensating transactions in reverse order
            context.executeCompensationsInReverse();
            
            return SagaResult.failed(state, e.getMessage());
        }
    }
}

// Saga Context to manage compensations
public class SagaContext {
    private final Map<String, Object> data = new HashMap<>();
    private final LinkedList<CompensationAction> compensations = new LinkedList<>();
    
    public void addCompensation(String name, Runnable action) {
        compensations.addFirst(new CompensationAction(name, action)); // Add to front for reverse order
    }
    
    public void executeCompensationsInReverse() {
        for (CompensationAction compensation : compensations) {
            try {
                log.info("Executing compensation: {}", compensation.name());
                compensation.action().run();
            } catch (Exception e) {
                log.error("Compensation {} failed: {}", compensation.name(), e.getMessage());
                // Log but continue with other compensations
            }
        }
    }
    
    public void set(String key, Object value) { data.put(key, value); }
    public <T> T get(String key) { return (T) data.get(key); }
}

public enum SagaState {
    STARTED, ORDER_CREATED, INVENTORY_RESERVED, PAYMENT_PROCESSED, COMPLETED, FAILED
}

Outbox Pattern

Ensures reliable event publishing by storing events in the same database as the business data.

┌─────────────────────────────────────────────────────┐
│                  Order Service                       │
│  ┌─────────────────────────────────────────────────┐│
│  │            Single Database Transaction          ││
│  │  ┌─────────────┐      ┌─────────────────────┐  ││
│  │  │ Orders Table│      │    Outbox Table     │  ││
│  │  │ INSERT order│  +   │ INSERT event record │  ││
│  │  └─────────────┘      └─────────────────────┘  ││
│  └─────────────────────────────────────────────────┘│
└───────────────────────────┬─────────────────────────┘
                            │
                    ┌───────▼───────┐
                    │  CDC / Poller │  (Debezium, Polling)
                    └───────┬───────┘
                            │
                    ┌───────▼───────┐
                    │  Message Bus  │  (Kafka, RabbitMQ)
                    └───────────────┘

Implementation:

@Service
public class OrderService {
    
    @Transactional // Single transaction for both
    public Order createOrder(OrderRequest request) {
        // 1. Save order
        Order order = orderRepository.save(new Order(request));
        
        // 2. Save event to outbox (same transaction)
        OutboxEvent event = OutboxEvent.builder()
            .aggregateType("Order")
            .aggregateId(order.getId())
            .type("OrderCreated")
            .payload(objectMapper.writeValueAsString(order))
            .createdAt(Instant.now())
            .build();
        outboxRepository.save(event);
        
        return order;
    }
}

// Separate process polls outbox and publishes to Kafka
@Scheduled(fixedDelay = 1000)
public void publishOutboxEvents() {
    List<OutboxEvent> events = outboxRepository.findUnpublished();
    for (OutboxEvent event : events) {
        try {
            kafkaTemplate.send(event.getType(), event.getPayload());
            event.markPublished();
            outboxRepository.save(event);
        } catch (Exception e) {
            log.error("Failed to publish event: {}", event.getId());
        }
    }
}

TCC Pattern (Try-Confirm-Cancel)

A reservation-based pattern for financial transactions.

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│    Account   │     │   Inventory  │     │   Shipping   │
│    Service   │     │    Service   │     │    Service   │
└──────┬───────┘     └──────┬───────┘     └──────┬───────┘
       │                    │                    │
 ┌─────┴─────┐        ┌─────┴─────┐        ┌─────┴─────┐
 │   TRY     │        │   TRY     │        │   TRY     │
 │ Reserve   │        │ Reserve   │        │ Reserve   │
 │   $500    │        │  5 items  │        │  slot     │
 └─────┬─────┘        └─────┬─────┘        └─────┬─────┘
       │                    │                    │
       │         ALL TRY SUCCESSFUL?             │
       │                    │                    │
 ┌─────┴─────┐        ┌─────┴─────┐        ┌─────┴─────┐
 │  CONFIRM  │        │  CONFIRM  │        │  CONFIRM  │
 │  Deduct   │        │  Deduct   │        │  Assign   │
 │   $500    │        │  5 items  │        │   slot    │
 └───────────┘        └───────────┘        └───────────┘

         OR (if any TRY fails)

 ┌─────┴─────┐        ┌─────┴─────┐        ┌─────┴─────┐
 │  CANCEL   │        │  CANCEL   │        │  CANCEL   │
 │  Release  │        │  Release  │        │  Release  │
 │   $500    │        │  5 items  │        │   slot    │
 └───────────┘        └───────────┘        └───────────┘

Implementation:

public interface TccService<T> {
    T tryReserve(ReservationRequest request);    // Reserve resources
    void confirm(String reservationId);           // Commit the reservation
    void cancel(String reservationId);            // Release the reservation
}

@Service
public class AccountTccService implements TccService<AccountReservation> {
    
    @Override
    public AccountReservation tryReserve(ReservationRequest request) {
        Account account = accountRepository.findById(request.getAccountId());
        
        if (account.getAvailableBalance() < request.getAmount()) {
            throw new InsufficientFundsException();
        }
        
        // Reserve funds (don't deduct yet)
        account.setReservedAmount(account.getReservedAmount() + request.getAmount());
        accountRepository.save(account);
        
        return new AccountReservation(UUID.randomUUID().toString(), request);
    }
    
    @Override
    public void confirm(String reservationId) {
        AccountReservation reservation = reservationRepository.findById(reservationId);
        Account account = accountRepository.findById(reservation.getAccountId());
        
        // Actually deduct the money
        account.setBalance(account.getBalance() - reservation.getAmount());
        account.setReservedAmount(account.getReservedAmount() - reservation.getAmount());
        accountRepository.save(account);
        
        reservationRepository.delete(reservation);
    }
    
    @Override
    public void cancel(String reservationId) {
        AccountReservation reservation = reservationRepository.findById(reservationId);
        Account account = accountRepository.findById(reservation.getAccountId());
        
        // Release reserved funds
        account.setReservedAmount(account.getReservedAmount() - reservation.getAmount());
        accountRepository.save(account);
        
        reservationRepository.delete(reservation);
    }
}

Idempotency Implementation

Ensures operations produce the same result when called multiple times.

Why Idempotency Matters:

Client ──Request──▶ Server ──Timeout──▶ Client retries
                          │
                    Did it succeed?
                    Did it fail?
                    Should I retry?

Without idempotency: Customer might be charged twice!
With idempotency: Safe to retry, server returns cached result

Implementation:

@Entity
@Table(name = "idempotency_keys")
public class IdempotencyRecord {
    @Id
    private String key;                    // Client-provided unique key
    
    @Column(length = 10000)
    private String responseBody;           // Cached response
    
    private Integer responseStatus;        // HTTP status code
    private Instant createdAt;
    private Instant expiresAt;             // TTL (e.g., 24 hours)
    
    @Enumerated(EnumType.STRING)
    private ProcessingStatus status;       // PROCESSING, COMPLETED, FAILED
}

@Service
public class IdempotentPaymentService {
    
    private final IdempotencyRepository idempotencyRepo;
    private final PaymentProcessor paymentProcessor;
    
    @Transactional
    public ResponseEntity<PaymentResponse> processPayment(
            String idempotencyKey, 
            PaymentRequest request) {
        
        // 1. Check if already processed
        Optional<IdempotencyRecord> existing = idempotencyRepo.findById(idempotencyKey);
        
        if (existing.isPresent()) {
            IdempotencyRecord record = existing.get();
            
            // Still processing - return 409 Conflict
            if (record.getStatus() == ProcessingStatus.PROCESSING) {
                return ResponseEntity.status(409)
                    .body(new PaymentResponse("Request still processing"));
            }
            
            // Already completed - return cached response
            return ResponseEntity.status(record.getResponseStatus())
                .body(objectMapper.readValue(record.getResponseBody(), PaymentResponse.class));
        }
        
        // 2. Create record with PROCESSING status
        IdempotencyRecord record = IdempotencyRecord.builder()
            .key(idempotencyKey)
            .status(ProcessingStatus.PROCESSING)
            .createdAt(Instant.now())
            .expiresAt(Instant.now().plus(24, ChronoUnit.HOURS))
            .build();
        idempotencyRepo.save(record);
        
        try {
            // 3. Process the payment
            PaymentResponse response = paymentProcessor.process(request);
            
            // 4. Update record with result
            record.setStatus(ProcessingStatus.COMPLETED);
            record.setResponseStatus(200);
            record.setResponseBody(objectMapper.writeValueAsString(response));
            idempotencyRepo.save(record);
            
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            // 5. Update record with failure
            record.setStatus(ProcessingStatus.FAILED);
            record.setResponseStatus(500);
            record.setResponseBody(objectMapper.writeValueAsString(
                new PaymentResponse("Payment failed: " + e.getMessage())));
            idempotencyRepo.save(record);
            
            throw e;
        }
    }
}

// Controller
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
    
    @PostMapping
    public ResponseEntity<PaymentResponse> createPayment(
            @RequestHeader("Idempotency-Key") String idempotencyKey,
            @Valid @RequestBody PaymentRequest request) {
        return idempotentPaymentService.processPayment(idempotencyKey, request);
    }
}

Client-side usage:

// Frontend generates unique key
const idempotencyKey = crypto.randomUUID();

// Safe to retry on network failure
async function createPayment(paymentData) {
  for (let attempt = 1; attempt <= 3; attempt++) {
    try {
      const response = await fetch('/api/payments', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Idempotency-Key': idempotencyKey  // Same key for all retries
        },
        body: JSON.stringify(paymentData)
      });
      
      if (response.ok || response.status !== 409) {
        return response.json();
      }
      
      // 409 = still processing, wait and retry
      await sleep(1000 * attempt);
    } catch (error) {
      if (attempt === 3) throw error;
      await sleep(1000 * attempt);
    }
  }
}

Choosing the Right Pattern

ScenarioRecommended Pattern
E-commerce order flowSaga (Orchestration) - Clear steps, easy rollback
Event-driven microservicesSaga (Choreography) + Outbox - Loose coupling
Financial transactionsTCC - Strong consistency guarantees
Payment processingSaga + Idempotency - Safe retries
Legacy system integrationTwo-Phase Commit - When supported
Audit-heavy domainsEvent Sourcing - Complete history

Key Takeaways

  1. Avoid distributed transactions when possible - Design services to be eventually consistent
  2. Sagas are the most common solution - Use orchestration for complex flows, choreography for simple ones
  3. Always implement idempotency - Essential for safe retries and reliability
  4. Outbox pattern ensures reliable messaging - No lost events
  5. Compensating transactions must be idempotent - They may be called multiple times
  6. Monitor saga state - Track pending, completed, and failed sagas

19. Quality Gates for Financial Systems

Pipeline Quality Gates

quality_gates:
  # Gate 1: Code Quality
  code_quality:
    - lint_errors: 0
    - type_check_errors: 0
    - code_smells: < 10 per 1000 LOC
    - duplications: < 3%
    
  # Gate 2: Test Coverage
  test_coverage:
    - unit_test_coverage: >= 80%
    - branch_coverage: >= 75%
    - critical_path_coverage: 100%
    - mutation_score: >= 70%
    
  # Gate 3: Security
  security:
    - critical_vulnerabilities: 0
    - high_vulnerabilities: 0
    - medium_vulnerabilities: < 5
    - secrets_detected: 0
    - SAST_issues: 0 critical/high
    - DAST_issues: 0 critical/high
    
  # Gate 4: Performance
  performance:
    - api_response_p95: < 500ms
    - api_response_p99: < 1000ms
    - memory_leak_detected: false
    - load_test_error_rate: < 0.1%
    
  # Gate 5: Compliance
  compliance:
    - license_violations: 0
    - PCI_DSS_checks: pass
    - SOC2_controls: pass
    - audit_logging: enabled

Implementation in CI/CD

# GitLab CI Example
stages:
  - build
  - test
  - security
  - quality-gate
  - deploy

quality_gate:
  stage: quality-gate
  script:
    - |
      # Fetch metrics
      COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
      CRITICAL_VULNS=$(cat security-report.json | jq '.criticalCount')
      SONAR_STATUS=$(curl -s "$SONAR_URL/api/qualitygates/project_status?projectKey=$PROJECT")
      
      # Evaluate gates
      if [ "$COVERAGE" -lt 80 ]; then
        echo "FAILED: Coverage $COVERAGE% < 80%"
        exit 1
      fi
      
      if [ "$CRITICAL_VULNS" -gt 0 ]; then
        echo "FAILED: $CRITICAL_VULNS critical vulnerabilities found"
        exit 1
      fi
      
      if [ "$(echo $SONAR_STATUS | jq -r '.projectStatus.status')" != "OK" ]; then
        echo "FAILED: SonarQube quality gate failed"
        exit 1
      fi
      
      echo "All quality gates passed!"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Financial System Specific Gates

  • Four-Eyes Principle: Require 2 approvers for production deployments
  • Change Window Enforcement: Only deploy during approved windows
  • Rollback Readiness: Automated rollback verification
  • Data Integrity Checks: Pre/post deployment validation
  • Regulatory Compliance: Automated compliance checking

20. Performance Balancing Strategy

Frontend Performance

OptimizationTechnique
Bundle SizeCode splitting, tree shaking, lazy loading
CachingService workers, HTTP cache headers
RenderingSSR/SSG for initial load, skeleton screens
ImagesWebP format, lazy loading, CDN
Critical PathInline critical CSS, defer non-essential JS
// Code Splitting Example
const Dashboard = lazy(() => import('./Dashboard'));
const Reports = lazy(() => import('./Reports'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/reports" element={<Reports />} />
      </Routes>
    </Suspense>
  );
}

BFF Latency Optimization

OptimizationTechnique
Parallel CallsCompletableFuture, reactive streams
CachingRedis for frequently accessed data
Response ShapingReturn only needed fields
Connection PoolingHTTP client connection reuse
Compressiongzip/brotli for responses
// Parallel API calls
public DashboardResponse getDashboard(String userId) {
    CompletableFuture<Profile> profile = CompletableFuture.supplyAsync(
        () -> profileService.get(userId), executor);
    CompletableFuture<List<Transaction>> transactions = CompletableFuture.supplyAsync(
        () -> transactionService.getRecent(userId), executor);
    CompletableFuture<Balance> balance = CompletableFuture.supplyAsync(
        () -> accountService.getBalance(userId), executor);
    
    return CompletableFuture.allOf(profile, transactions, balance)
        .thenApply(v -> DashboardResponse.builder()
            .profile(profile.join())
            .transactions(transactions.join())
            .balance(balance.join())
            .build())
        .join();
}

Backend Data Consistency

PatternUse Case
Eventual ConsistencyRead replicas, caching, search indexes
Strong ConsistencyFinancial transactions, balances
CQRSSeparate read/write models
Event SourcingAudit trail, temporal queries

Balancing Strategy Matrix

                   ┌─────────────────────────────────────────┐
                   │         Performance vs Consistency      │
                   ├─────────────────────────────────────────┤
    High           │  Cache + Eventual   │  Cache + Strong   │
    Performance    │  (Product catalog)  │  (User sessions)  │
                   ├─────────────────────┼───────────────────┤
    Balanced       │  Async Processing   │  Sync + Retries   │
                   │  (Notifications)    │  (Order creation) │
                   ├─────────────────────┼───────────────────┤
    High           │  Queue + Saga       │  2PC / Saga       │
    Consistency    │  (Inventory)        │  (Payments)       │
                   └─────────────────────┴───────────────────┘
                        Eventual              Strong
                        Consistency           Consistency

Practical Guidelines

  1. Identify Critical Paths: Which operations MUST be strongly consistent?
  2. Accept Eventual Consistency: For non-critical reads (user preferences, analytics)
  3. Cache Strategically:
    • Cache at CDN for static content
    • Cache at BFF for aggregated data
    • Cache at service for frequent queries
  4. Set SLOs: Define acceptable latency per endpoint
  5. Monitor & Iterate: Use APM to identify bottlenecks
Performance Budget Example:
┌──────────────────────────────────────────────────────────┐
│ User Action: View Account Dashboard                      │
├──────────────────────────────────────────────────────────┤
│ FE Initial Load:    < 1.5s (LCP)                        │
│ FE Time to Interactive: < 2.5s                          │
│ BFF Response Time:  < 200ms (p95)                       │
│ BE Service Calls:   < 100ms each (parallel)             │
│ Database Queries:   < 50ms                              │
│ Total User Wait:    < 2s                                │
└──────────────────────────────────────────────────────────┘

Summary

This guide covers the essential topics for modern full-stack development with React, Java, and cloud-native architectures. Key takeaways:

  1. React: Understand component composition, state management, and performance optimization
  2. TypeScript: Leverage type safety for better developer experience
  3. Testing: Use the test pyramid approach with appropriate tools
  4. CI/CD: Automate quality gates and deployments
  5. Microservices: Design for resilience, observability, and loose coupling
  6. Cloud: Use managed services, implement autoscaling, and prioritize security
  7. Resilience: Implement circuit breakers, retries, and bulkheads
  8. Security: Defense in depth with OAuth2, validation, and secrets management
  9. Distributed Systems: Use Saga pattern and ensure idempotency
  10. Performance: Balance user experience with data consistency requirements

Quick Reference Answers

Q1: Describe the roles of components, props, and state in React and when to split a component.

Answer:

  • Components: Reusable, self-contained building blocks that encapsulate UI logic and structure. They can be functional (preferred) or class-based.
  • Props: Read-only data passed from parent to child components. They enable customization and flow unidirectionally (top-down).
  • State: Mutable data managed within a component that triggers re-renders when changed. Managed via useState hook.
  • When to Split: Split when a component exceeds 200-300 lines, has multiple responsibilities, contains repeated patterns, has complex nested conditionals, or when parts update at different frequencies.

Q2: What are the main differences between ES6 JavaScript and TypeScript in a React project?

Answer:

AspectES6 JavaScriptTypeScript
Type SystemDynamicStatic with annotations
Compile-time ErrorsNoneCatches errors before runtime
IDE SupportBasicRich IntelliSense & refactoring
Interfaces/GenericsNot supportedFull support
Build StepOptionalRequired (tsc)
RefactoringError-proneSafe with type checking

TypeScript provides better maintainability, self-documenting code, and catches bugs earlier in development.

Example 1: Props Definition

// ES6 JavaScript - No type safety
function UserCard({ name, age, onUpdate }) {
  return <div onClick={() => onUpdate(name)}>{name} - {age}</div>;
}
// Problem: No way to know what props are expected or their types
// Can accidentally pass wrong types: <UserCard name={123} age="twenty" />
// TypeScript - Full type safety
interface UserCardProps {
  name: string;
  age: number;
  email?: string; // Optional prop
  onUpdate: (name: string) => void;
}

const UserCard: React.FC<UserCardProps> = ({ name, age, email, onUpdate }) => {
  return <div onClick={() => onUpdate(name)}>{name} - {age}</div>;
};
// Benefit: IDE shows errors immediately if wrong props are passed
// <UserCard name={123} /> // ❌ Error: Type 'number' is not assignable to type 'string'

Example 2: State Management

// ES6 JavaScript
const [user, setUser] = useState(null);
// Later...
console.log(user.name); // ❌ Runtime error if user is null!
// TypeScript
interface User {
  id: number;
  name: string;
  email: string;
}

const [user, setUser] = useState<User | null>(null);
// Later...
console.log(user.name); // ❌ Compile error: Object is possibly 'null'
console.log(user?.name); // ✅ Safe access with optional chaining

Example 3: API Response Handling

// ES6 JavaScript - No guarantee of response shape
async function fetchUsers() {
  const response = await fetch('/api/users');
  const data = await response.json();
  return data; // What properties does data have? 🤷
}

// Using the data
const users = await fetchUsers();
users.forEach(u => console.log(u.nmae)); // Typo! Won't catch until runtime
// TypeScript - Typed API responses
interface ApiResponse<T> {
  data: T;
  meta: { total: number; page: number };
}

interface User {
  id: number;
  name: string;
  email: string;
}

async function fetchUsers(): Promise<ApiResponse<User[]>> {
  const response = await fetch('/api/users');
  const data: ApiResponse<User[]> = await response.json();
  return data;
}

// Using the data
const { data: users } = await fetchUsers();
users.forEach(u => console.log(u.nmae)); // ❌ Error: Property 'nmae' does not exist. Did you mean 'name'?

Example 4: Event Handlers

// ES6 JavaScript
function handleChange(e) {
  console.log(e.target.value); // Works, but no autocomplete
  console.log(e.target.checked); // Is this a checkbox? Who knows!
}
// TypeScript - Precise event types
function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
  console.log(e.target.value); // ✅ Autocomplete works
}

function handleSelectChange(e: React.ChangeEvent<HTMLSelectElement>) {
  console.log(e.target.value);
  console.log(e.target.selectedIndex); // ✅ Select-specific properties available
}

function handleFormSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
  const formData = new FormData(e.currentTarget);
}

Example 5: Enums and Union Types

// ES6 JavaScript - Magic strings everywhere
function setStatus(status) {
  if (status === 'pending') { /* ... */ }
  else if (status === 'aprovved') { /* Typo! */ }
}
setStatus('pnding'); // Another typo, no error
// TypeScript - Type-safe status values
type OrderStatus = 'pending' | 'approved' | 'rejected' | 'shipped';

function setStatus(status: OrderStatus) {
  if (status === 'pending') { /* ... */ }
  else if (status === 'aprovved') { /* ❌ Error: not in union type */ }
}
setStatus('pnding'); // ❌ Error: Argument of type '"pnding"' is not assignable

// Or use enums
enum PaymentStatus {
  Pending = 'PENDING',
  Completed = 'COMPLETED',
  Failed = 'FAILED',
}

function processPayment(status: PaymentStatus) {
  switch (status) {
    case PaymentStatus.Pending: // ✅ Autocomplete available
      break;
    case PaymentStatus.Completed:
      break;
    // TypeScript warns if you miss a case!
  }
}

Example 6: Custom Hooks

// ES6 JavaScript
function useAuth() {
  const [user, setUser] = useState(null);
  const login = (email, password) => { /* ... */ };
  const logout = () => { /* ... */ };
  return { user, login, logout };
}
// No way to know what this hook returns without reading the code
// TypeScript - Self-documenting hooks
interface User {
  id: string;
  email: string;
  role: 'admin' | 'user';
}

interface AuthContext {
  user: User | null;
  isLoading: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

function useAuth(): AuthContext {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  const login = async (email: string, password: string): Promise<void> => {
    // Implementation
  };

  const logout = (): void => {
    setUser(null);
  };

  return { user, isLoading, login, logout };
}
// Hover over useAuth() to see exactly what it returns!

Q3: Name three accessibility best practices when building React UIs.

Answer:

  1. Semantic HTML: Use proper HTML elements (
  2. ARIA Labels and Roles: Add aria-label, aria-describedby, and role attributes to provide context for assistive technologies, especially for icon-only buttons and dynamic content.
  3. Keyboard Navigation: Ensure all interactive elements are focusable (tabIndex), respond to keyboard events (Enter, Escape), and maintain visible focus indicators.

Example 1: Semantic HTML Elements

// ❌ Bad - Using divs for everything
function BadNavigation() {
  return (
    <div className="nav">
      <div className="nav-item" onClick={goHome}>Home</div>
      <div className="nav-item" onClick={goAbout}>About</div>
      <div className="nav-item" onClick={goContact}>Contact</div>
    </div>
  );
}

// ✅ Good - Semantic HTML
function GoodNavigation() {
  return (
    <nav aria-label="Main navigation">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/contact">Contact</a></li>
      </ul>
    </nav>
  );
}
// ❌ Bad - Div as button
<div className="btn" onClick={handleSubmit}>Submit</div>

// ✅ Good - Actual button element
<button type="submit" onClick={handleSubmit}>Submit</button>

// ❌ Bad - Div as input container with no structure
<div>
  <span>Email</span>
  <input type="email" />
</div>

// ✅ Good - Proper label association
<div>
  <label htmlFor="email">Email</label>
  <input type="email" id="email" name="email" />
</div>

Example 2: ARIA Labels and Roles

// Icon-only buttons need labels
// ❌ Bad - No context for screen readers
<button onClick={onClose}>
  <CloseIcon />
</button>

// ✅ Good - With aria-label
<button onClick={onClose} aria-label="Close dialog">
  <CloseIcon aria-hidden="true" />
</button>

// ✅ Alternative - With visually hidden text
<button onClick={onClose}>
  <CloseIcon aria-hidden="true" />
  <span className="sr-only">Close dialog</span>
</button>
// Live regions for dynamic content updates
function NotificationBanner({ message }) {
  return (
    // aria-live announces changes to screen readers
    <div 
      role="alert" 
      aria-live="polite" 
      aria-atomic="true"
    >
      {message}
    </div>
  );
}

// Loading states
function DataTable({ isLoading, data }) {
  return (
    <div 
      aria-busy={isLoading} 
      aria-live="polite"
    >
      {isLoading ? (
        <div role="status">
          <span className="sr-only">Loading data...</span>
          <Spinner aria-hidden="true" />
        </div>
      ) : (
        <table>{/* data */}</table>
      )}
    </div>
  );
}
// Descriptive links
// ❌ Bad - Ambiguous link text
<a href="/docs">Click here</a>
<a href="/report">Read more</a>

// ✅ Good - Descriptive link text
<a href="/docs">View documentation</a>
<a href="/report">Read the full quarterly report</a>

// ✅ Good - With aria-describedby for additional context
<article>
  <h2 id="article-title">Understanding React Hooks</h2>
  <p id="article-desc">A comprehensive guide to useState and useEffect...</p>
  <a href="/article/1" aria-describedby="article-title article-desc">
    Read full article
  </a>
</article>

Example 3: Keyboard Navigation

// Modal with proper focus management
function AccessibleModal({ isOpen, onClose, children }) {
  const modalRef = useRef(null);
  const previousFocusRef = useRef(null);

  useEffect(() => {
    if (isOpen) {
      // Store current focus to restore later
      previousFocusRef.current = document.activeElement;
      // Move focus to modal
      modalRef.current?.focus();
    } else if (previousFocusRef.current) {
      // Restore focus when modal closes
      previousFocusRef.current.focus();
    }
  }, [isOpen]);

  // Handle Escape key
  const handleKeyDown = (e) => {
    if (e.key === 'Escape') {
      onClose();
    }
  };

  if (!isOpen) return null;

  return (
    <div 
      className="modal-overlay" 
      onClick={onClose}
      onKeyDown={handleKeyDown}
    >
      <div
        ref={modalRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby="modal-title"
        tabIndex={-1}
        onClick={(e) => e.stopPropagation()}
      >
        <h2 id="modal-title">Modal Title</h2>
        {children}
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  );
}
// Custom interactive component with keyboard support
function AccessibleDropdown({ options, value, onChange }) {
  const [isOpen, setIsOpen] = useState(false);
  const [activeIndex, setActiveIndex] = useState(0);

  const handleKeyDown = (e) => {
    switch (e.key) {
      case 'Enter':
      case ' ':
        e.preventDefault();
        if (isOpen) {
          onChange(options[activeIndex]);
          setIsOpen(false);
        } else {
          setIsOpen(true);
        }
        break;
      case 'ArrowDown':
        e.preventDefault();
        if (isOpen) {
          setActiveIndex((prev) => Math.min(prev + 1, options.length - 1));
        } else {
          setIsOpen(true);
        }
        break;
      case 'ArrowUp':
        e.preventDefault();
        setActiveIndex((prev) => Math.max(prev - 1, 0));
        break;
      case 'Escape':
        setIsOpen(false);
        break;
      case 'Tab':
        setIsOpen(false);
        break;
    }
  };

  return (
    <div
      role="combobox"
      aria-expanded={isOpen}
      aria-haspopup="listbox"
      aria-controls="dropdown-list"
      tabIndex={0}
      onKeyDown={handleKeyDown}
      onClick={() => setIsOpen(!isOpen)}
    >
      <span>{value || 'Select an option'}</span>
      {isOpen && (
        <ul id="dropdown-list" role="listbox">
          {options.map((option, index) => (
            <li
              key={option}
              role="option"
              aria-selected={index === activeIndex}
              onClick={() => {
                onChange(option);
                setIsOpen(false);
              }}
            >
              {option}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Example 4: Focus Indicators

/* ❌ Bad - Removing focus outline completely */
button:focus {
  outline: none;
}

/* ✅ Good - Custom visible focus indicator */
button:focus {
  outline: none;
  box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.6);
}

/* ✅ Better - Using focus-visible for keyboard-only focus */
button:focus {
  outline: none;
}

button:focus-visible {
  outline: 2px solid #4A90D9;
  outline-offset: 2px;
}

Example 5: Skip Links

// Allow keyboard users to skip repetitive navigation
function Layout({ children }) {
  return (
    <>
      <a href="#main-content" className="skip-link">
        Skip to main content
      </a>
      <header>
        <Navigation /> {/* Many links to tab through */}
      </header>
      <main id="main-content" tabIndex={-1}>
        {children}
      </main>
    </>
  );
}

// CSS for skip link
// .skip-link {
//   position: absolute;
//   left: -9999px;
//   z-index: 999;
// }
// .skip-link:focus {
//   left: 10px;
//   top: 10px;
//   background: #000;
//   color: #fff;
//   padding: 8px 16px;
// }

Example 6: Form Accessibility

function AccessibleForm() {
  const [errors, setErrors] = useState({});

  return (
    <form aria-describedby="form-instructions">
      <p id="form-instructions">
        Fields marked with * are required
      </p>

      {/* Required field with error handling */}
      <div>
        <label htmlFor="email">
          Email <span aria-hidden="true">*</span>
          <span className="sr-only">(required)</span>
        </label>
        <input
          type="email"
          id="email"
          name="email"
          required
          aria-required="true"
          aria-invalid={!!errors.email}
          aria-describedby={errors.email ? 'email-error' : undefined}
        />
        {errors.email && (
          <span id="email-error" role="alert" className="error">
            {errors.email}
          </span>
        )}
      </div>

      {/* Password with requirements */}
      <div>
        <label htmlFor="password">Password</label>
        <input
          type="password"
          id="password"
          name="password"
          aria-describedby="password-requirements"
        />
        <p id="password-requirements" className="hint">
          Must be at least 8 characters with one number and one special character
        </p>
      </div>

      {/* Accessible button */}
      <button type="submit">
        Create Account
      </button>
    </form>
  );
}

Example 7: Images and Media

// Informative images need alt text
<img src="chart.png" alt="Sales chart showing 25% growth in Q4 2025" />

// Decorative images should be hidden
<img src="decorative-border.png" alt="" role="presentation" />

// Complex images with detailed descriptions
<figure>
  <img 
    src="architecture-diagram.png" 
    alt="System architecture diagram"
    aria-describedby="diagram-desc"
  />
  <figcaption id="diagram-desc">
    The diagram shows three microservices connected via an API gateway. 
    The User Service handles authentication, the Order Service manages 
    transactions, and the Notification Service sends emails and SMS.
  </figcaption>
</figure>

// Video with captions
<video controls>
  <source src="tutorial.mp4" type="video/mp4" />
  <track kind="captions" src="captions.vtt" srcLang="en" label="English" />
  <track kind="descriptions" src="descriptions.vtt" srcLang="en" label="Audio descriptions" />
</video>

Screen Reader Only Utility Class:

/* Visually hidden but accessible to screen readers */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

Q4: What are the minimal CI steps for a Front-End project before deployment?

Answer:

  1. Install Dependencies: npm ci for reproducible builds
  2. Linting: ESLint to enforce code quality standards
  3. Type Checking: TypeScript compilation check (tsc --noEmit)
  4. Unit Tests: Run Jest/Vitest tests with coverage reporting
  5. Build: Production build to verify compilation succeeds

Optional additions: Security audit (npm audit), bundle size analysis, and E2E tests for critical paths.


Q5: What are the characteristics of a good RESTful API?

Answer:

  • Stateless: Each request contains all necessary information
  • Resource-Based URLs: Use nouns (/users/{id}), not verbs (/getUser)
  • Proper HTTP Methods: GET (read), POST (create), PUT (replace), PATCH (partial update), DELETE (remove)
  • Consistent Response Format: Standardized structure with data, meta, errors
  • Appropriate Status Codes: 200, 201, 204, 400, 401, 403, 404, 500
  • Versioning: /api/v1/ for backward compatibility
  • HATEOAS: Include hypermedia links for discoverability
  • Pagination, Filtering, Sorting: Query parameters for large datasets

Q6: Why use a Java BFF (Backend for Frontend) and what are the main benefits?

Answer:

Origin: The BFF pattern was coined by Sam Newman at SoundCloud around 2015 to solve problems with microservices architectures where frontends had to make multiple API calls and handle complex data aggregation.

The Problem Before BFF:

  • Frontend needed 5-10 API calls to render one page
  • Over-fetching/under-fetching data
  • Different clients (web, mobile, TV) needed different data shapes
  • Internal service details exposed to clients
  • Complex data transformation logic in the browser

What is BFF: A dedicated backend layer tailored to specific frontend needs, sitting between the frontend and microservices.

When to Use BFF:

  • Multiple clients with different data needs
  • Frontend making 5+ API calls per page
  • Need to hide internal APIs for security
  • Different teams own frontend and backend

Main Benefits:

  1. API Aggregation: Combine multiple backend calls into a single response
  2. Response Shaping: Transform data specifically for UI needs
  3. Security Layer: Handle authentication, hide internal service details
  4. Caching: Cache frequently accessed data to reduce backend load
  5. Protocol Translation: Convert gRPC/SOAP to REST/GraphQL
  6. Resilience: Implement circuit breakers, retries, and fallbacks
  7. Rate Limiting: Protect downstream services from traffic spikes

Why Java: JVM performance, strong typing, mature ecosystem (Spring Boot, Resilience4j), excellent async support (CompletableFuture), and enterprise team familiarity.

BFF vs API Gateway: API Gateway handles routing/auth (infra team owns), BFF handles data aggregation/transformation (frontend team owns). Often used together: Client → API Gateway → BFF → Microservices


Q7: How do you apply Scrum/Agile to ensure quality?

Answer:

  • Definition of Done (DoD): Include code review, tests, coverage thresholds, documentation, and accessibility requirements
  • Sprint Planning: Include testing effort in story estimates
  • Daily Standups: Surface blockers and quality issues early
  • Pair Programming: Real-time code review and knowledge sharing
  • Test-Driven Development (TDD): Write tests before implementation
  • Retrospectives: Identify quality improvement areas and act on them
  • Continuous Integration: Automated quality gates on every commit
  • Technical Debt Sprints: Allocate capacity for refactoring and improvements
  • Shift-Left Testing: Test early in the development cycle

Q8: How do you optimize unnecessary re-renders in React?

Answer:

How React Re-rendering Works:

  1. Trigger: State change (setState), props change, parent re-render, or context change
  2. Render Phase: React calls component function to create new Virtual DOM
  3. Reconciliation: React diffs new vs old Virtual DOM
  4. Commit Phase: React updates only changed parts in actual DOM

Key Concept - Shallow Comparison: React uses shallow comparison to detect changes:

  • Primitives: Compares values (5 === 5 → true)
  • Objects/Arrays: Compares references, NOT contents ({a:1} === {a:1} → false, different memory references)

Why This Causes Problems:

// Every render creates NEW objects with NEW references
const user = { name: 'John' };     // New reference each render
const items = [1, 2, 3];            // New reference each render
const onClick = () => {};           // New reference each render
// Even with React.memo, child re-renders because references changed!

Solutions:

ProblemSolution
Child re-renders when props unchangedReact.memo(Component)
Expensive calculation every renderuseMemo(() => calculation, [deps])
Function prop causes re-renderuseCallback(() => fn, [deps])
State affects unrelated componentsState Colocation (move state down)
Rendering thousands of itemsVirtualization (react-window)
Inline objects/arrays in propsDefine outside component or use useMemo

Debugging: Use React DevTools Profiler to identify which components re-render and why.

Golden Rule: Don't optimize prematurely—profile first, then optimize actual bottlenecks.


Q9: When do you use Jest vs Selenium/E2E for Front-End testing?

Answer:

Use Jest When:Use Selenium/E2E When:
Testing individual functions/componentsTesting complete user journeys
Fast feedback needed (ms per test)Cross-browser compatibility required
Testing hooks and component behaviorIntegration with real APIs needed
Mocking external dependenciesVisual regression testing
Running in CI frequentlyValidating critical paths (login, checkout)
70-80% of your test suite10-20% of your test suite

Rule of Thumb: Jest for unit/component tests (fast, reliable), E2E for critical user flows (slower, more realistic).


Q10: When would you choose GraphQL over REST in microservices?

Answer:

Choose GraphQL when:

  • Multiple clients have different data needs
  • Data is deeply nested or highly relational
  • Frontend needs rapid iteration without backend changes
  • Reducing over-fetching/under-fetching is critical
  • Real-time subscriptions are required

Choose REST when:

  • Simple CRUD operations dominate
  • File uploads/downloads are common
  • HTTP caching is important
  • Team is more familiar with REST
  • Building public APIs

In Microservices: GraphQL works well as a gateway (federation) that aggregates multiple services, providing a unified API while keeping services independently deployable.


Q11: What technical characteristics should a well-designed microservice have?

Answer:

  1. Single Responsibility: One business capability per service
  2. Independently Deployable: Deploy without affecting other services
  3. Loosely Coupled: Minimal dependencies on other services
  4. Owns Its Data: Dedicated database per service (no shared databases)
  5. API-First: Well-defined contracts (OpenAPI/AsyncAPI)
  6. Observable: Built-in logging, metrics, and distributed tracing
  7. Resilient: Handles failures gracefully (circuit breakers, retries)
  8. Stateless: No local session state (externalize to cache/DB)
  9. Containerized: Runs in Docker/Kubernetes for portability
  10. Health Checks: Liveness and readiness probes for orchestration

Q12: How do you manage unit/component test coverage and mocking?

Answer:

Coverage Strategy (Test Pyramid):

  • Unit Tests: 70% (business logic, utilities)
  • Integration Tests: 20% (service boundaries)
  • E2E Tests: 10% (critical paths)

Coverage Targets:

  • Business Logic: 90%+
  • Components: 80%+
  • Overall: 80% line coverage, 75% branch coverage

Mocking Approaches:

  1. Jest Mocks: jest.mock() for modules, jest.spyOn() for functions
  2. MSW (Mock Service Worker): Mock API responses at network level
  3. Component Mocks: Replace heavy components with lightweight test doubles
  4. Dependency Injection: Design for testability by injecting dependencies

Enforcement: Use Jest's coverageThreshold in configuration to fail CI if coverage drops below thresholds.


Q13: Describe a standard CI/CD pipeline for FE & BE from commit to deployment.

Answer:

Frontend Pipeline:

  1. Build: Checkout → Install deps (npm ci) → Lint → Type check → Build
  2. Test: Unit tests → Coverage report → SonarQube scan
  3. Security: npm audit → Snyk/OWASP scan
  4. Deploy Staging: Upload to S3 → Invalidate CloudFront → E2E tests
  5. Deploy Production: Manual approval → Deploy → Smoke tests → Notify

Backend Pipeline:

  1. Build: Checkout → Maven compile → Package → Docker build
  2. Test: Unit tests → Integration tests → Coverage (JaCoCo) → SonarQube
  3. Security: OWASP dependency-check → Trivy container scan → Secrets scan
  4. Deploy: Push to ECR → Update ECS task → Health check → Rollback on failure

Key Practices: Artifacts passed between stages, parallel jobs where possible, manual approval gates for production.


Q14: How do you deploy a React FE and Java BE to AWS?

Answer:

React Frontend:

  • S3: Host static files (HTML, JS, CSS)
  • CloudFront: CDN for global distribution, HTTPS, caching
  • Route 53: DNS management
  • Certificate Manager: SSL/TLS certificates
  • WAF: Web Application Firewall for security

Java Backend:

  • ECR: Store Docker images
  • ECS Fargate (or EKS): Run containers without managing servers
  • ALB: Load balancing and health checks
  • RDS/Aurora: Managed database
  • ElastiCache: Redis for caching/sessions
  • Secrets Manager: Store credentials securely

Deployment Flow: CI builds artifacts → Push to S3/ECR → Update CloudFront/ECS → Health checks → Rollback if needed.


Q15: For a cloud-first banking system, which AWS services, autoscaling, and observability would you choose?

Answer:

Core Services:

  • Compute: EKS/ECS Fargate (containerized, auto-scaling)
  • Database: Aurora PostgreSQL (Multi-AZ, ACID compliance)
  • Cache: ElastiCache Redis
  • Queue: SQS/EventBridge for async processing
  • API: API Gateway with throttling

Auto-Scaling:

  • ECS: Target tracking (CPU 70%, Memory 75%)
  • Aurora: Auto-scaling read replicas (2-15)
  • Step scaling for traffic spikes

Observability:

  • Logging: CloudWatch Logs + Log Insights (or OpenSearch)
  • Metrics: CloudWatch + Prometheus/Grafana
  • Tracing: X-Ray or OpenTelemetry
  • Alerting: CloudWatch Alarms → SNS → PagerDuty
  • APM: X-Ray Service Map

Security: WAF, Shield, KMS encryption, CloudTrail audit logs, VPC with private subnets.


Q16: In a Java BFF, how do you implement circuit breaker, retry/backoff, bulkhead, and timeouts?

Answer: Use Resilience4j library:

Circuit Breaker:

@CircuitBreaker(name = "service", fallbackMethod = "fallback")
public Response call() { return client.call(); }
// Config: slidingWindowSize=10, failureRateThreshold=50%, waitDurationInOpenState=30s

Retry with Exponential Backoff:

@Retry(name = "service")
// Config: maxAttempts=3, waitDuration=1s, exponentialBackoffMultiplier=2

Bulkhead (Isolation):

@Bulkhead(name = "service", type = Bulkhead.Type.THREADPOOL)
// Config: maxConcurrentCalls=20, maxThreadPoolSize=10, queueCapacity=100

Timeout:

@TimeLimiter(name = "service")
public CompletableFuture<Response> call() { ... }
// Config: timeoutDuration=3s

Combine all patterns on critical methods for maximum resilience.


Q17: How do you secure APIs with OAuth2/OIDC, JWT, rate limiting, validation, and secrets management on AWS?

Answer:

OAuth2/OIDC: Configure Spring Security with oauth2ResourceServer(), validate JWTs against identity provider (Cognito, Okta).

JWT Validation: Verify issuer, audience, expiration, and signature. Extract roles/permissions from claims.

Rate Limiting:

  • API Gateway: Configure throttling (1000 req/s, burst 2000)
  • Application: Resilience4j RateLimiter

Input Validation: Use Bean Validation (@Valid, @NotBlank, @Email, @Pattern) on request DTOs.

Secrets Management:

  • Store credentials in AWS Secrets Manager
  • Rotate secrets automatically
  • Never commit secrets to code
  • Use IAM roles for service-to-service auth

Additional: WAF rules, TLS 1.3, security headers, audit logging.


Q18: How do you handle distributed transactions with Saga and ensure idempotency?

Answer:

The Problem: In microservices, each service has its own database, so traditional ACID transactions don't work across services. We need patterns that handle failures gracefully.

Distributed Transaction Solutions:

PatternDescriptionBest For
SagaSequence of local transactions with compensationsLong-running business processes
OutboxStore events in same DB, publish asynchronouslyEvent-driven architectures
TCCTry-Confirm-Cancel (reservation-based)Financial transactions
Event SourcingStore state as sequence of eventsAudit-heavy domains

Saga Pattern (Two Types):

  1. Choreography: Services communicate via events, no central coordinator

    • Pros: Loose coupling, no single point of failure
    • Cons: Hard to track state, complex debugging
  2. Orchestration: Central coordinator manages the flow

    • Pros: Clear flow, easy debugging, centralized error handling
    • Cons: Single point of failure, tighter coupling

Saga Implementation:

try {
    order = orderService.create(request);
    context.addCompensation(() -> orderService.cancel(order.getId()));
    
    inventoryService.reserve(order.getItems());
    context.addCompensation(() -> inventoryService.release(order.getItems()));
    
    paymentService.charge(order);
    // Success
} catch (Exception e) {
    context.executeCompensationsInReverse(); // Rollback in reverse order
}

TCC Pattern (Try-Confirm-Cancel):

  1. Try: Reserve resources in all services
  2. Confirm: If all succeed, commit all reservations
  3. Cancel: If any fail, release all reservations

Outbox Pattern: Write events to an outbox table in the same transaction as business data, then publish asynchronously. Guarantees no lost events.

Idempotency (Essential for Safe Retries):

  • Accept Idempotency-Key header from client
  • Store key + response in database before processing
  • Check for existing key before processing new requests
  • Return cached response if key already exists
  • Set TTL on idempotency records (e.g., 24 hours)

Key Principles:

  • Design for eventual consistency when possible
  • Compensating transactions must also be idempotent
  • Always monitor saga states (pending, completed, failed)
  • Log everything for debugging distributed flows

Q19: How do you design quality gates in the pipeline for financial-grade systems?

Answer:

Gate 1 - Code Quality:

  • Zero lint/type errors
  • Code smells < 10 per 1000 LOC
  • Duplication < 3%

Gate 2 - Test Coverage:

  • Unit test coverage ≥ 80%
  • Branch coverage ≥ 75%
  • Critical paths: 100%

Gate 3 - Security:

  • Zero critical/high vulnerabilities
  • No secrets in code
  • SAST/DAST clean

Gate 4 - Performance:

  • API p95 < 500ms
  • No memory leaks
  • Load test error rate < 0.1%

Gate 5 - Compliance:

  • License compliance
  • PCI-DSS/SOC2 controls pass
  • Audit logging enabled

Additional Controls:

  • Four-eyes principle (2 approvers for production)
  • Change window enforcement
  • Automated rollback readiness
  • Pre/post deployment data integrity checks

Q20: How do you balance FE performance, BFF latency, and BE data consistency?

Answer:

Frontend Performance:

  • Code splitting and lazy loading
  • CDN caching for static assets
  • Service workers for offline support
  • Skeleton screens for perceived performance

BFF Latency:

  • Parallel API calls (CompletableFuture)
  • Redis caching for frequently accessed data
  • Response shaping (return only needed fields)
  • Connection pooling
  • gzip/brotli compression

Backend Consistency:

  • Strong consistency for financial transactions (Saga, 2PC)
  • Eventual consistency for non-critical data (caching, search indexes)
  • CQRS for separating read/write models

Balancing Strategy:

  1. Identify critical paths requiring strong consistency
  2. Accept eventual consistency for reads (product catalogs, analytics)
  3. Cache strategically at each layer (CDN → BFF → Service)
  4. Set SLOs per endpoint
  5. Monitor with APM and iterate

Example Budget:

  • FE Initial Load: < 1.5s (LCP)
  • BFF Response: < 200ms (p95)
  • BE Service Calls: < 100ms each (parallel)
  • Total User Wait: < 2s

Last Updated: January 2026

Duong Ngo

Duong Ngo

Full-Stack AI Developer with 12+ years of experience

Comments