How Graphs Are Stored and Traversed in Java (DFS & BFS Explained)

 

How Graphs Are Stored and Traversed in Java (DFS & BFS Explained)

Graphs are everywhere: social networks, maps, workflows, dependencies, and more.
In this post, we’ll see:

  • How to store a graph in Java

  • How to print the graph

  • How DFS (Depth-First Search) and BFS (Breadth-First Search) work

  • A simple implementation with main()

We’ll keep everything very simple and beginner-friendly.


1. The Example Graph

We’ll use this small undirected graph with 5 nodes: 0, 1, 2, 3, 4.

🔹 Visual graph (text-based)

1 / \ 0 2 | 3 1 | 4

🔹 Edges

We have these connections (edges):

  • 0 — 1

  • 0 — 3

  • 1 — 2

  • 1 — 4

This is an undirected graph, so:

  • If 0 is connected to 1 → 1 is also connected to 0.


2. How We Store the Graph in Java

The most common way to store a graph is using an Adjacency List.

2.1 What is an adjacency list?

For each node, we store a list of its neighbors.

For our graph:

Node 0 → neighbors: 1, 3 Node 1 → neighbors: 0, 2, 4 Node 2 → neighbors: 1 Node 3 → neighbors: 0 Node 4 → neighbors: 1

2.2 Java data structure

We use:

List<List<Integer>> graph = new ArrayList<>();
  • graph is a list

  • Each element of graph is another list of integers

  • graph.get(i) gives the list of neighbors for node i

So:

graph.get(0) → [1, 3] graph.get(1) → [0, 2, 4] graph.get(2) → [1] graph.get(3) → [0] graph.get(4) → [1]

2.3 Step-by-step: building the adjacency list

Assume we have n = 5 nodes: 0..4.

Step 1: Create the outer list

int n = 5; List<List<Integer>> graph = new ArrayList<>();

Now graph is empty:

graph: [ ]

Step 2: Create an empty neighbor list for each node

for (int i = 0; i < n; i++) { graph.add(new ArrayList<>()); }

Now we have:

graph[0] = [ ] graph[1] = [ ] graph[2] = [ ] graph[3] = [ ] graph[4] = [ ]

Step 3: Add edges (undirected)

For undirected edge 0 — 1:

graph.get(0).add(1); // 0 → 1 graph.get(1).add(0); // 1 → 0

For 0 — 3:

graph.get(0).add(3); graph.get(3).add(0);

For 1 — 2:

graph.get(1).add(2); graph.get(2).add(1);

For 1 — 4:

graph.get(1).add(4); graph.get(4).add(1);

Now the adjacency list looks like:

graph[0] = [1, 3] graph[1] = [0, 2, 4] graph[2] = [1] graph[3] = [0] graph[4] = [1]

3. Printing the Graph

We can print the adjacency list like this:

static void printGraph(List<List<Integer>> graph) { for (int i = 0; i < graph.size(); i++) { System.out.print("Node " + i + " -> "); System.out.println(graph.get(i)); } }

Output:

Node 0 -> [1, 3] Node 1 -> [0, 2, 4] Node 2 -> [1] Node 3 -> [0] Node 4 -> [1]

This gives a clear picture of how the graph is stored in memory.


4. DFS (Depth-First Search) – Step by Step

DFS means: go as deep as possible along one path before backtracking.

We will start DFS from node 0.

4.1 DFS idea in simple words

  1. Start at a node (e.g., 0)

  2. Visit it and mark as visited

  3. For each neighbor:

    • If not visited → go into that neighbor (recursively)

  4. When no unvisited neighbors → go back (backtrack)

4.2 Run DFS on our graph

Adjacency list reminder:

0 : [1, 3] 1 : [0, 2, 4] 2 : [1] 3 : [0] 4 : [1]

Start: DFS(0)

Let’s track:

  • visited[] = keeps track of nodes already seen

  • output = order of printing

Step-by-step DFS from 0

  1. Start at 0

    • Visit 0 → visited[0] = true

    • Output: 0

    • Neighbors: [1, 3]

  2. Go to neighbor 1 (first neighbor of 0)

    • Visit 1 → visited[1] = true

    • Output: 0 1

    • Neighbors of 1: [0, 2, 4]

    • 0 is already visited → skip

    • Next: 2

  3. Go to neighbor 2

    • Visit 2 → visited[2] = true

    • Output: 0 1 2

    • Neighbors of 2: [1] (already visited)

    • No more new neighbors → go back to 1

  4. Back at 1, next neighbor is 4

    • Visit 4 → visited[4] = true

    • Output: 0 1 2 4

    • Neighbors of 4: [1] (already visited)

    • No more new neighbors → go back to 1 → back to 0

  5. Back at 0, next neighbor is 3

    • Visit 3 → visited[3] = true

    • Output: 0 1 2 4 3

    • Neighbors of 3: [0] (already visited)

    • Done

Final DFS order starting from 0:

0 1 2 4 3

4.3 DFS Java code

static void dfs(int node, boolean[] visited, List<List<Integer>> graph) { visited[node] = true; // Mark current node as visited System.out.print(node + " "); // Print the node // Go through all neighbors of this node for (int neighbor : graph.get(node)) { if (!visited[neighbor]) { // If neighbor is not visited dfs(neighbor, visited, graph); // Recursively visit neighbor } } }

5. BFS (Breadth-First Search) – Step by Step

BFS means: visit nodes level by level, like waves going outwards.

We use a queue.

5.1 BFS idea in simple words

  1. Start at a node and push it into a queue

  2. While queue is not empty:

    • Remove (poll) one node

    • Visit it

    • Add all its unvisited neighbors to the queue

5.2 Run BFS from node 0

Adjacency list again:

0 : [1, 3] 1 : [0, 2, 4] 2 : [1] 3 : [0] 4 : [1]

Let’s track:

  • visited[]

  • queue

  • output

Initial:

queue = [0] visited[0] = true output = (empty)

Step 1:

  • Take from queue → node = 0

  • Output: 0

  • Neighbors: [1, 3]

    • 1 not visited → mark visited, add to queue

    • 3 not visited → mark visited, add to queue

Now:

queue = [1, 3] visited = [0,1,0,1,0] output = 0

Step 2:

  • Take from queue → node = 1

  • Output: 0 1

  • Neighbors of 1: [0, 2, 4]

    • 0 already visited → skip

    • 2 not visited → mark visited, add to queue

    • 4 not visited → mark visited, add to queue

Now:

queue = [3, 2, 4] visited = [1,1,1,1,1] output = 0 1

Step 3:

  • Take from queue → node = 3

  • Output: 0 1 3

  • Neighbors of 3: [0] (already visited)

  • Queue: [2, 4]

Step 4:

  • Take from queue → node = 2

  • Output: 0 1 3 2

  • Neighbors of 2: [1] (already visited)

  • Queue: [4]

Step 5:

  • Take from queue → node = 4

  • Output: 0 1 3 2 4

  • Neighbors of 4: [1] (already visited)

  • Queue: [] → done

Final BFS order from 0:

0 1 3 2 4

5.3 BFS Java code

static void bfs(int start, List<List<Integer>> graph) { boolean[] visited = new boolean[graph.size()]; Queue<Integer> q = new LinkedList<>(); visited[start] = true; // Mark starting node q.add(start); // Add start node into queue while (!q.isEmpty()) { int node = q.poll(); // Take one node from queue System.out.print(node + " "); for (int neighbor : graph.get(node)) { if (!visited[neighbor]) { // If neighbor not visited visited[neighbor] = true; q.add(neighbor); // Add neighbor to queue } } } }

6. Full Simple Java Program (With main)

You can copy-paste this file and run directly.

import java.util.*; public class GraphDemo { // ---------- DFS: Depth-First Search ---------- static void dfs(int node, boolean[] visited, List<List<Integer>> graph) { visited[node] = true; // Mark current node as visited System.out.print(node + " "); // Print the node // Visit all neighbors for (int neighbor : graph.get(node)) { if (!visited[neighbor]) { // Only go to unvisited nodes dfs(neighbor, visited, graph); } } } // ---------- BFS: Breadth-First Search ---------- static void bfs(int start, List<List<Integer>> graph) { boolean[] visited = new boolean[graph.size()]; Queue<Integer> q = new LinkedList<>(); visited[start] = true; // Start node is visited q.add(start); // Put start node in queue while (!q.isEmpty()) { int node = q.poll(); // Take from front of queue System.out.print(node + " "); // Add all unvisited neighbors to queue for (int neighbor : graph.get(node)) { if (!visited[neighbor]) { visited[neighbor] = true; q.add(neighbor); } } } } // ---------- Print Graph (Adjacency List) ---------- static void printGraph(List<List<Integer>> graph) { System.out.println("Graph adjacency list:"); for (int i = 0; i < graph.size(); i++) { System.out.println("Node " + i + " -> " + graph.get(i)); } } public static void main(String[] args) { int n = 5; // Number of nodes: 0,1,2,3,4 // 1) Create empty graph List<List<Integer>> graph = new ArrayList<>(); for (int i = 0; i < n; i++) { graph.add(new ArrayList<>()); } // 2) Add undirected edges: 0-1, 0-3, 1-2, 1-4 addUndirectedEdge(graph, 0, 1); addUndirectedEdge(graph, 0, 3); addUndirectedEdge(graph, 1, 2); addUndirectedEdge(graph, 1, 4); // 3) Print graph printGraph(graph); // 4) Run DFS from node 0 System.out.print("\nDFS from node 0: "); boolean[] visited = new boolean[n]; dfs(0, visited, graph); // 5) Run BFS from node 0 System.out.print("\nBFS from node 0: "); bfs(0, graph); } // Helper to add an undirected edge static void addUndirectedEdge(List<List<Integer>> graph, int u, int v) { graph.get(u).add(v); // u -> v graph.get(v).add(u); // v -> u } }

Example output:

Graph adjacency list: Node 0 -> [1, 3] Node 1 -> [0, 2, 4] Node 2 -> [1] Node 3 -> [0] Node 4 -> [1] DFS from node 0: 0 1 2 4 3 BFS from node 0: 0 1 3 2 4

Concurrency in 2025: Java Virtual Threads vs Golang vs Spring Reactive

 

Concurrency in 2025: Java Virtual Threads vs Golang vs Spring Reactive

Modern backend systems require:

  • High concurrency

  • Low latency

  • Efficient I/O handling

  • Minimal thread blocking

  • Simple programming models

Three major concurrency paradigms dominate:

1️⃣ Java Virtual Threads (Project Loom)
2️⃣ Golang Goroutines
3️⃣ Spring WebFlux Reactive (Non-blocking)

All three achieve millions of concurrent tasks, but the internal architecture is very different.


1. Java Virtual Threads (Java 21 → Java 25)

Java Virtual Threads = lightweight threads managed by the JVM, not the OS.

✔ Key Features

  • 1 million+ virtual threads possible

  • Extremely cheap to create

  • Uses structured concurrency

  • Blocking code is OK — JVM converts blocking to non-blocking

  • Works with existing libraries (JDBC, sockets, HTTP clients)

  • No need for callbacks, reactive streams, or futures

This preserves classic imperative Java code, but with goroutine-like performance.


How Virtual Threads Work Internally

Virtual Threads → Scheduled onto few OS Threads (carrier threads)

When a virtual thread blocks (e.g., socket read):

  • JVM parks it

  • Frees the carrier OS thread

  • Schedules another virtual thread

This is identical to how goroutines work.


✔ Best For

  • REST APIs

  • Microservices

  • Multi-database systems

  • Thread-per-request models

  • Concurrency without complexity


2. Golang Goroutines

Golang introduced goroutines long before Java Virtual Threads.

✔ Key Features

  • Extremely lightweight (2 KB stack)

  • Millions of goroutines

  • Channel-based communication

  • Non-blocking I/O via Go runtime

  • Go scheduler handles everything


How Goroutines Work

Same idea:

Many Goroutines → Few OS Threads

Go runtime handles:

  • Work stealing

  • Scheduling

  • Stack growth

  • Network poller

This allows easy high concurrency without reactive code.


✔ Best For

  • Network servers

  • Microservices

  • Cloud-native apps

  • CLI tools

  • Distributed systems


3. Spring WebFlux (Reactive Programming)

Spring WebFlux uses:

  • Event-loop

  • Non-blocking Netty

  • Reactive Streams

  • Mono / Flux

WebFlux uses reactive pipelines instead of threads.

✔ Key Features

  • Single-digit threads can handle thousands of requests

  • Fully async & non-blocking

  • Backpressure support

  • Requires reactive programming model


How WebFlux Works Internally

Event Loop (Netty) → Callback-based I/O → Reactor pipelines (Mono/Flux) → No blocking allowed

If any blocking code is used → system slows down.


✔ Best For

  • High-load APIs

  • Long-polling

  • Streaming

  • Real-time apps


Comparison Table: Java Virtual Threads vs Golang vs WebFlux

(2025 Edition)

FeatureJava Virtual ThreadsGolangSpring WebFlux
ModelLightweight threadsLightweight goroutinesEvent-loop reactive
ComplexityLow (imperative code)LowHigh (reactive)
Blocking IOAllowedAllowed❌ Not allowed
PerformanceHighHighVery High
ScalabilityMillions threadsMillions goroutinesTens of thousands connections
Learning CurveVery easyVery easyHard
EcosystemHuge (Java libs work)Native Go libsLimited to reactive libs
DebuggingEasyEasyHard
ParadigmThread-per-requestGoroutinesReactive pipelines
Suitable forMicroservices, APIMicroservices, network appsHigh throughput reactive apps

When to Choose What?


👉 Choose Java Virtual Threads when:

  • You already use Spring MVC or Java EE

  • You want simple blocking code but scalable

  • You want to modernize existing apps

  • You want concurrency without rewriting everything


👉 Choose Golang when:

  • You need simple + scalable microservices

  • You prefer static binaries

  • You want a language built for concurrency

  • You are building distributed systems / networking tools


👉 Choose WebFlux when:

  • You need extremely high throughput

  • You want true reactive streams

  • You handle streaming / SSE / websocket

  • You’re OK with Mono/Flux complexity


Code Comparison: Same API in All Three Models


⭐ Virtual Threads (Java 25)

var server = HttpServer.create(); server.createContext("/hello", exchange -> { Thread.startVirtualThread(() -> { var response = "Hello!"; exchange.sendResponseHeaders(200, response.length()); exchange.getResponseBody().write(response.getBytes()); }); }); server.start();

⭐ Golang Goroutine

http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { go func() { w.Write([]byte("Hello!")) }() }) http.ListenAndServe(":8080", nil)

⭐ Spring WebFlux (Reactive)

@GetMapping("/hello") public Mono<String> hello() { return Mono.just("Hello!"); }

Final Summary

CategoryWinner
Easiest ConcurrencyJava Virtual Threads / Go
Most powerfulWebFlux
Best for beginnersGo
Best for enterpriseJava Virtual Threads
Best performance when done rightWebFlux
Best debuggingJava Virtual Threads
Best ecosystemJava (Spring Boot)

One-Line Interview Answer

Java Virtual Threads bring Java concurrency to the same lightweight model used by Golang goroutines. WebFlux uses a non-blocking event-loop model, which is more complex but gives maximum throughput. Virtual Threads are easiest to adopt and are the future of Java concurrency.

Distributed Tracing in Spring Boot using OpenTelemetry + Jaeger (2025 Guide)

 

Distributed Tracing in Spring Boot using OpenTelemetry + Jaeger (2025 Guide)

This guide helps you understand:

✅ What is distributed tracing
✅ How to enable OpenTelemetry in Spring Boot
✅ How Jaeger receives spans
✅ What is the difference between OTEL vs Prometheus
✅ Complete ready-to-run example with Docker Compose
✅ Architecture diagrams
✅ README for your project


1. What is Distributed Tracing?

Modern microservices often involve multiple services communicating with each other:

Client → API Gateway → Auth Service → Order Service → Inventory Service

Debugging such systems with only logs is extremely difficult.

Distributed Tracing solves this by:

✔ Tracking a request end-to-end
✔ Showing how long each service took
✔ Identifying bottlenecks
✔ Detecting where a failure occurs

OpenTelemetry (OTEL) is the standard for collecting such traces.


2. What is OpenTelemetry (OTEL)?

OpenTelemetry is an open-source CNCF standard for:

  • Traces

  • Metrics

  • Logs

In your Spring Boot app, OTEL:

  1. Creates spans

  2. Stores them briefly in memory

  3. Exports them through OTLP protocol

  4. Sends to a backend like Jaeger, Tempo, Zipkin, etc.

✔ OTEL uses a push model

This means:

Your application sends data OUT to Jaeger, like this:

Spring Boot (OTEL SDK) ↓ (push) Jaeger Collector

3. What is Jaeger?

Jaeger is an open-source distributed tracing system.

In local mode (all-in-one):

  • Collector

  • Query

  • UI

  • Storage

are all in one container.

Jaeger stores data:

  • In memory (in local mode)

  • Elastic / Cassandra / ClickHouse / etc. (in production)


4. Architecture Diagram (Spring Boot → OTEL → Jaeger)

Client | v Spring Boot App | | creates spans v OTel SDK | | batches spans (memory buffer) v OTLP Exporter | | pushes spans v Jaeger Collector (4317/4318) | v Jaeger Storage (Memory) | v Jaeger UI (16686)

5. How Does OTEL Export Data? (Important)

👉 OTEL does not store long-term data
👉 OTEL only batches spans in memory for milliseconds
👉 OTEL pushes spans to Jaeger via OTLP

There is no polling.

✔ Jaeger never calls your app
✔ Your app always pushes the data


6. What is Prometheus?

Prometheus is used for:

  • Metrics (CPU, memory, request_count, latency, etc.)

  • Alerting

  • Time-series storage

Prometheus follows a pull model:

Prometheus scrapes /actuator/prometheus endpoint

✔ Prometheus calls your Spring Boot endpoint

✔ Your app does NOT push data

✔ Prometheus stores metrics on disk


7. OTEL vs Prometheus (VERY IMPORTANT)

FeatureOpenTelemetryPrometheus
PurposeTracing (request flow)Metrics (performance)
Data TypeSpans, traces, logsTime-series metrics
Collection ModelPush (App → Jaeger)Pull (Prometheus → App)
How It WorksOTEL sends trace data instantlyPrometheus periodically scrapes
What It ShowsEnd-to-end request timingCPU, memory, errors, latency
StorageJaeger (memory/DB)Prometheus TSDB
UIJaeger UIGrafana
Ideal UseMicroservice debuggingMonitoring & alerting

✔ Both used together in production

You use:

OTEL + Jaeger for tracing
Prometheus + Grafana for metrics


8. Spring Boot Example (Tracing /hello endpoint)

Your controller:

@GetMapping("/hello") public String hello() { Span span = tracer.spanBuilder("custom-span").startSpan(); span.addEvent("processing-hello"); try { Thread.sleep(50); } catch(Exception ignored) {} span.end(); return "Hello with tracing!"; }

Every request generates a trace visible in Jaeger.


9. Docker Compose — Spring Boot + Jaeger

version: "3.8" services: spring-app: build: . image: springboot-tracing-demo:latest container_name: springboot-tracing environment: - OTEL_SERVICE_NAME=springboot-tracing-demo - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 - OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf - OTEL_TRACES_EXPORTER=otlp ports: - "8080:8080" depends_on: - jaeger jaeger: image: jaegertracing/all-in-one:1.57 container_name: jaeger ports: - "16686:16686" - "4317:4317" - "4318:4318"

10. Source code

https://github.com/kkvinodkumaran/springboot-tracing-demo-complete

Circuit Breaker in Spring Boot — Complete Guide With Real Example (2025 Edition)

 

Circuit Breaker in Spring Boot — Complete Guide With Real Example (2025 Edition)

Modern microservices constantly depend on other services — internal API calls, third-party services, databases, payment gateways, or internal microservice-to-microservice communication.

But what happens when one of those services becomes slow or unstable?

  • Your threads get stuck

  • Your service slows down

  • Requests start timing out

  • Your system collapses (cascading failure)

To prevent this, we use the Circuit Breaker Pattern.


1. What Is a Circuit Breaker?

A Circuit Breaker prevents your application from continuously calling a failing service.

It works like an electrical breaker:

  • Closed → ON → working normally

  • Open → OFF → stop calling the service

  • Half-Open → testing the service


2. Why Do We Use a Circuit Breaker?

ProblemWhat Circuit Breaker Does
Slow external APIStops further calls; returns fallback
Service downPrevents thread blocking
Too many failuresOpens circuit quickly
Cascading failuresProtects upstream services
Improves reliabilityFail fast instead of waiting

3. Circuit Breaker Lifecycle (Simple Diagram)

┌──────────────┐ │ CLOSED │ ← everything normal └───────┬──────┘ │ failures exceed threshold ▼ ┌──────────────┐ │ OPEN │ ← stop calling service └───────┬──────┘ │ wait duration expires ▼ ┌──────────────┐ │ HALF-OPEN │ ← send limited test calls └───────┬──────┘ │ success ▼ ┌──────────────┐ │ CLOSED │ ← service recovered └──────────────┘

4. What Happens When Multiple Failures Occur?

Consider this configuration:

failure-rate-threshold: 50 sliding-window-size: 10 minimum-number-of-calls: 5 wait-duration-in-open-state: 5s

✔ Step-by-Step Example

You call an endpoint 5 times:

Call 1 → FAIL Call 2 → FAIL Call 3 → FAIL Call 4 → FAIL Call 5 → FAIL

Now:

Total calls = 5 Failures = 5 Failure rate = 100% (greater than threshold 50%)

✔ Circuit Breaker immediately switches to OPEN.


5. What Happens in OPEN State?

NO calls are sent to the real endpoint.
⭐ Every call is immediately failed by the circuit breaker.
⭐ The fallback method is executed instantly.

This is called FAIL-FAST.

So yes:

“If 5 failures happen, the circuit will stop hitting that endpoint for a few seconds/minutes until it recovers.”


6. HALF-OPEN State (Testing Recovery)

After the wait-duration passes (5 seconds):

  • Circuit moves to HALF-OPEN

  • Allows limited number of test calls
    (permitted-number-of-calls-in-half-open-state)

If test calls succeed → circuit goes back to CLOSED
If test calls fail → circuit goes back to OPEN


7. CLOSED State (Recovered)

Once enough successful calls occur, the service is considered healthy again.

Circuit becomes CLOSED, and traffic flows normally.


8. Implementing Circuit Breaker in Spring Boot (Using Resilience4j)

Resilience4j is the most modern and recommended fault-tolerance library for Spring Boot (replacing Netflix Hystrix).


✔ Step 1: Add Dependencies (pom.xml)

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot3</artifactId> </dependency>

✔ Step 2: Add Configuration (application.yml)

resilience4j: circuitbreaker: instances: externalServiceCB: failure-rate-threshold: 50 sliding-window-size: 10 minimum-number-of-calls: 5 wait-duration-in-open-state: 5s permitted-number-of-calls-in-half-open-state: 3

✔ Step 3: Create a Service That Calls an External API

@Service public class ExternalApiService { private final RestTemplate restTemplate = new RestTemplate(); @CircuitBreaker(name = "externalServiceCB", fallbackMethod = "fallback") public String callExternalService() { // Simulating a failing service String url = "https://example.com/api/data"; return restTemplate.getForObject(url, String.class); } public String fallback(Exception ex) { return "Fallback: External service unavailable"; } }

✔ Step 4: Add Controller

@RestController public class DemoController { @Autowired private ExternalApiService apiService; @GetMapping("/get-data") public String getData() { return apiService.callExternalService(); } }

9. Testing the Circuit Breaker

Case A — External API is down

✔ First 5 attempts → failures
✔ Circuit goes OPEN
✔ All next requests return immediately:

Fallback: External service unavailable

Case B — After 5 seconds

✔ Circuit enters HALF-OPEN
✔ Allows 3 test calls
✔ If successful → closes
✔ If fails → stays open


10. Real Use Cases of Circuit Breaker

✔ Microservices communication

E.g., Order Service → Payment Service

✔ 3rd party systems

SMS gateways, Stripe, PayPal, email APIs.

✔ Database outage

Fail fast instead of waiting for JDBC timeout.

✔ Cloud-native systems

Network hiccups are common.

✔ High-traffic applications

Avoid saturating threads and CPU.


11. Final Summary (Interview-Ready)

  • Circuit Breaker protects your app from calling failing services.

  • After repeated failures, the circuit opens.

  • In OPEN state, service calls are blocked immediately.

  • Fallback method handles the failure gracefully.

  • After wait time, HALF-OPEN state tests service health.

  • If OK → CLOSE; if fail → OPEN again.

  • Prevents cascading failures and improves reliability.

12 classic String-based Java interview questions with simple explanations and code.

  1️⃣ Check if a String is a Palindrome Problem Given a string, check if it reads the same forward and backward. Example: "madam...

Featured Posts