DEV Community

Cover image for Questions I Was Asked in My Kaiser Interview: Java, Spring Boot, Kafka, React and More
Renuka Patil
Renuka Patil

Posted on

Questions I Was Asked in My Kaiser Interview: Java, Spring Boot, Kafka, React and More

Interview experiences can be helpful when you’re preparing for your own next opportunity. In my Kaiser interview, I was asked questions across Java, Spring Boot and microservices, Kafka, databases, React, Git and CI/CD, as well as a few coding questions.
I’m sharing the questions and answers here so other developers can see what came up and use them as a starting point for their own preparation. Interview questions can vary by role and interviewer, but reviewing these topics helped me think through both the concepts and how they apply in real projects.

☕ Java

1. What is a Functional Interface? How do you use it with String?

A functional interface has exactly one abstract method. It can still have default or static methods. Functional interfaces are commonly used with lambda expressions and method references.

Java provides interfaces such as Predicate<T>, Function<T, R>, Consumer<T> and Supplier<T> in java.util.function.

import java.util.function.Function;

Function<String, Integer> lengthFinder = text -> text.length();

int length = lengthFinder.apply("Spring");
// 6
Enter fullscreen mode Exit fullscreen mode

Here, Function<String, Integer> accepts a String and returns an Integer. The lambda implements its single abstract method, apply.

You can also define your own:

@FunctionalInterface
interface StringFormatter {
    String format(String value);
}

StringFormatter formatter = value -> value.trim().toUpperCase();
System.out.println(formatter.format("  java  "));
// JAVA
Enter fullscreen mode Exit fullscreen mode

A method reference can make this shorter when an existing method already matches:

Function<String, String> uppercase = String::toUpperCase;
System.out.println(uppercase.apply("java")); // JAVA
Enter fullscreen mode Exit fullscreen mode

@FunctionalInterface is optional, but it asks the compiler to check that the interface has only one abstract method.


2. What are important String methods in Java?

String objects are immutable: methods that appear to change a string return a new string instead.

Method Purpose Example
length() Gets the number of characters "Java".length() → 4
isEmpty() Checks whether length is zero "".isEmpty() → true
isBlank() Checks whether the string is empty or whitespace (Java 11+) " ".isBlank() → true
charAt(index) Gets a character at an index "Java".charAt(1) → 'a'
substring(start, end) Gets part of a string; end is exclusive "Spring".substring(0, 3) → "Spr"
contains(text) Checks whether text occurs "Spring Boot".contains("Boot") → true
equals(other) Compares contents, case-sensitive "java".equals("Java") → false
equalsIgnoreCase(other) Compares contents, ignoring case "java".equalsIgnoreCase("Java") → true
compareTo(other) Lexicographically compares strings Useful for sorting
startsWith(prefix) / endsWith(suffix) Checks the beginning or end "file.csv".endsWith(".csv")
indexOf(text) / lastIndexOf(text) Finds a character or substring "banana".indexOf("a") → 1
toUpperCase() / toLowerCase() Changes letter case "Java".toLowerCase() → "java"
trim() / strip() Removes surrounding whitespace; strip() handles Unicode whitespace better " hi ".strip() → "hi"
replace(old, new) Replaces literal characters or text "a-b".replace("-", "_") → "a_b"
replaceAll(regex, replacement) Replaces text matched by a regular expression "a1b2".replaceAll("\\d", "") → "ab"
split(regex) Splits using a regular expression "a,b".split(",")
join(delimiter, values) Joins values into a string String.join("-", "a", "b") → "a-b"
format(...) Formats a string String.format("Hi, %s", "Renuka")

Use equals() to compare string contents. == checks whether two references point to the same object, which is usually not what you want.

String a = new String("Java");
String b = new String("Java");

System.out.println(a == b);      // false: different objects
System.out.println(a.equals(b)); // true: same contents
Enter fullscreen mode Exit fullscreen mode

For case-insensitive comparison in a specific language, use equalsIgnoreCase() or compare normalized strings with an explicit locale where appropriate.


3. What are Executors?

Executors are Java APIs for submitting tasks to managed threads. Instead of creating a new thread for every task, you give tasks to an executor, which controls how they run.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

ExecutorService executor = Executors.newFixedThreadPool(3);

executor.submit(() -> {
    System.out.println("Task running on " + Thread.currentThread().getName());
});

executor.shutdown();
Enter fullscreen mode Exit fullscreen mode

A fixed thread pool reuses up to three threads. This can reduce the overhead of repeatedly creating threads and limit how many tasks run at once.

Common executor choices include:

  • newFixedThreadPool(n): a fixed number of worker threads.
  • newSingleThreadExecutor(): runs tasks sequentially on one worker.
  • newScheduledThreadPool(n): supports delayed and recurring tasks.
  • newCachedThreadPool(): can grow and shrink the pool; use cautiously because it may create many threads.

In production, explicitly configure thread-pool size, queue capacity, and rejection behavior where needed. An unbounded queue or a pool that grows without a useful limit can cause resource problems under load.


4. What is CompletableFuture?

CompletableFuture represents a result that may become available later. It supports asynchronous work and lets you compose follow-up actions without manually coordinating threads.

import java.util.concurrent.CompletableFuture;

CompletableFuture<String> future =
    CompletableFuture.supplyAsync(() -> loadCustomerName(42))
        .thenApply(String::toUpperCase)
        .exceptionally(error -> "UNKNOWN");

String result = future.join();
Enter fullscreen mode Exit fullscreen mode

The example loads a name asynchronously, converts it to uppercase when available, and supplies a fallback if an exception occurs.

Useful methods include:

  • supplyAsync(...): runs a task that returns a value.
  • runAsync(...): runs a task with no returned value.
  • thenApply(...): transforms a result.
  • thenCompose(...): chains a task that itself returns a CompletableFuture.
  • thenCombine(...): combines two independent futures.
  • exceptionally(...) and handle(...): handle failures.

By default, async methods generally use the common ForkJoin pool. For blocking work or workloads needing specific limits, pass a dedicated Executor.


5. Difference between ExecutorService and CompletableFuture

They solve related but different parts of asynchronous programming.

ExecutorService CompletableFuture
Manages and runs submitted tasks on threads Represents an eventual result and lets you compose work
submit() returns a Future Offers chaining and combining methods such as thenApply() and thenCombine()
Useful for controlling a pool and scheduling tasks Useful for expressing asynchronous workflows
A basic Future does not provide convenient continuation chaining Supports result transformations and error-handling stages

They are often used together: give a CompletableFuture a configured Executor so the application controls the threads that run its work.

ExecutorService executor = Executors.newFixedThreadPool(4);

CompletableFuture<String> future =
    CompletableFuture.supplyAsync(() -> fetchData(), executor);

String data = future.join();
executor.shutdown();
Enter fullscreen mode Exit fullscreen mode

6. What are Parallel Streams and when should they be used?

A parallel stream divides stream work into pieces that can be processed by multiple threads, usually through the common ForkJoin pool.

long count = numbers.parallelStream()
    .filter(number -> number % 2 == 0)
    .count();
Enter fullscreen mode Exit fullscreen mode

Parallel streams may help when:

  • The collection is large enough to offset the overhead of splitting and combining work.
  • Each item can be processed independently.
  • The operation is CPU-intensive.
  • The source splits efficiently, as arrays often do.

They may hurt when the collection is small, the work is I/O-bound, order matters, or operations modify shared mutable state. They also use shared thread-pool resources by default.

For example, this is unsafe because several threads update the same variable:

int[] total = {0};

numbers.parallelStream().forEach(n -> total[0] += n); // race condition
Enter fullscreen mode Exit fullscreen mode

Prefer stream reductions that combine results safely:

int total = numbers.parallelStream()
    .mapToInt(Integer::intValue)
    .sum();
Enter fullscreen mode Exit fullscreen mode

Measure performance with realistic data before choosing parallel execution.


7. What is a Semaphore and how does it work?

A Semaphore limits how many threads can access a resource at the same time. It starts with a number of permits. A thread acquires a permit before entering a protected section and releases it when finished.

import java.util.concurrent.Semaphore;

Semaphore permits = new Semaphore(3);

void callExternalService() throws InterruptedException {
    permits.acquire();
    try {
        // At most three threads can run this section concurrently.
        makeRequest();
    } finally {
        permits.release();
    }
}
Enter fullscreen mode Exit fullscreen mode

With three permits, at most three threads can hold permits concurrently. A thread that cannot acquire a permit waits until one is released.

Semaphores are useful for limiting concurrent access to a resource such as a connection pool or external service. Always release permits in a finally block so exceptions do not permanently consume them.


8. How do you create APIs and handle errors/exceptions in Java Spring Boot?

A typical Spring Boot API has:

  1. A controller for HTTP routing and request/response handling.
  2. A service for business logic.
  3. A repository for data access.
  4. A DTO for the API contract.
  5. Validation and centralized exception handling.

Example request DTO:

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record CreateCustomerRequest(
    @NotBlank String name,
    @Email @NotBlank String email
) {}
Enter fullscreen mode Exit fullscreen mode

Controller:

import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/customers")
public class CustomerController {
    private final CustomerService service;

    public CustomerController(CustomerService service) {
        this.service = service;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public CustomerResponse create(
            @Valid @RequestBody CreateCustomerRequest request) {
        return service.create(request);
    }

    @GetMapping("/{id}")
    public CustomerResponse get(@PathVariable Long id) {
        return service.getById(id);
    }
}
Enter fullscreen mode Exit fullscreen mode

The service can throw a clear domain exception when an item is missing:

public CustomerResponse getById(Long id) {
    Customer customer = repository.findById(id)
        .orElseThrow(() -> new CustomerNotFoundException(id));

    return toResponse(customer);
}
Enter fullscreen mode Exit fullscreen mode

Centralize exception-to-HTTP mapping with @RestControllerAdvice:

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(CustomerNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ApiError handleNotFound(CustomerNotFoundException ex) {
        return new ApiError("CUSTOMER_NOT_FOUND", ex.getMessage());
    }

    @ExceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiError handleValidation(
            org.springframework.web.bind.MethodArgumentNotValidException ex) {
        return new ApiError("VALIDATION_ERROR", "Request validation failed");
    }
}

record ApiError(String code, String message) {}
Enter fullscreen mode Exit fullscreen mode

In a real API, return a consistent error format, avoid exposing internal stack traces or secrets, log enough context to diagnose failures, and use appropriate status codes. For example, 400 for invalid input, 404 for missing resources, and 500 for unexpected server errors.


🌐 Spring Boot and Microservices

9. How does caching work in Spring Boot?

Caching stores the result of an expensive operation so a later request can reuse it instead of repeating the work. Spring’s cache abstraction provides annotations that can work with providers such as Caffeine or Redis.

Enable caching:

@SpringBootApplication
@EnableCaching
public class Application {}
Enter fullscreen mode Exit fullscreen mode

Cache a method result:

@Service
public class ProductService {
    @Cacheable(cacheNames = "products", key = "#id")
    public Product getProduct(Long id) {
        return repository.findById(id)
            .orElseThrow(() -> new ProductNotFoundException(id));
    }

    @CacheEvict(cacheNames = "products", key = "#id")
    public void updateProduct(Long id, UpdateProductRequest request) {
        // Update the database.
    }
}
Enter fullscreen mode Exit fullscreen mode
  • @Cacheable returns the cached value when available; otherwise it runs the method and caches its result.
  • @CacheEvict removes an entry, often after an update or delete.
  • @CachePut runs the method and updates the cache with its result.

The key design questions are what to cache, how long it stays valid, how updates invalidate it, and whether multiple service instances share the cache. An in-memory cache is local to each process; Redis can provide a shared cache. Caching can serve stale data if expiry and invalidation are not designed carefully.


10. What is service registration/service discovery?

In a microservices environment, service instances may start, stop, or change address. Service registration records where a running instance can be reached. Service discovery lets a client find available instances without hard-coding their addresses.

A typical flow:

  1. A service instance starts.
  2. It registers its address and health status with a registry, or an infrastructure platform tracks it.
  3. A client asks for instances of a service.
  4. The client or a load balancer chooses an available instance.
  5. Health checks remove unhealthy instances from the available set.

This lets services scale or restart without requiring every caller to be manually reconfigured. In container platforms such as Kubernetes, service names and cluster networking commonly provide discovery. In other setups, a registry such as Eureka may be used.


11. How does load balancing work in microservices?

A load balancer distributes requests across several service instances. It can select instances using strategies such as:

  • Round robin: rotate through instances.
  • Least connections: choose the instance with fewer active connections.
  • Weighted routing: send more traffic to instances with more capacity.
  • Health-aware routing: exclude instances that fail health checks.

Load balancing can happen at different places:

  • Client-side: the caller discovers instances and chooses one.
  • Server-side: a proxy or gateway receives the request and forwards it.
  • Platform or infrastructure level: for example, Kubernetes service networking.

Load balancing improves capacity and availability, but it does not make a service fault tolerant by itself. Health checks, timeouts, retries, and appropriate data handling are also needed.


12. What is fault tolerance?

Fault tolerance is a system’s ability to continue providing useful service when some component fails.

Common techniques include:

  • Timeouts so callers do not wait indefinitely.
  • Limited retries with backoff for transient failures.
  • Circuit breakers to stop repeatedly calling a failing dependency.
  • Redundant service instances and health-aware load balancing.
  • Queues to absorb temporary spikes or allow asynchronous processing.
  • Fallback responses where a reduced result is acceptable.
  • Monitoring and recovery procedures.

For example, if a recommendation service is unavailable, a shopping application might still show products using a default ordering. Whether a fallback is appropriate depends on the business requirement.


13. What is a Circuit Breaker and how does it work?

A Circuit Breaker monitors calls to a dependency and temporarily blocks calls after repeated failures. This can prevent a failing dependency from consuming resources across the rest of the system.

It usually has three states:

  1. Closed: requests pass through; failures are counted.
  2. Open: requests are rejected or sent to a fallback without calling the dependency.
  3. Half-open: after a wait period, a small number of test requests are allowed. Success closes the circuit; continued failure opens it again.

For example, an order service calling a payment service could fail fast while payment is unavailable, rather than letting request threads wait on repeated timeouts.

A circuit breaker does not fix the underlying service. It limits the impact of failure. Configure its failure threshold, wait period, timeout, and fallback carefully. Retries and circuit breakers should be coordinated so that retrying does not increase pressure on an unhealthy dependency.


14. What is CQRS?

CQRS stands for Command Query Responsibility Segregation. It separates operations that change data (commands) from operations that read data (queries).

For example:

  • CreateOrder or CancelOrder is a command.
  • GetOrderDetails or ListCustomerOrders is a query.

In a simple system, commands and queries may use the same database while having separate application models. More complex systems may use different models or stores optimized for writes and reads.

CQRS can help when read and write workloads have very different needs, or when read views need to combine data from multiple sources. It also adds complexity: data may be eventually consistent, and teams must manage synchronization between models. It is not necessary for every CRUD application.


15. What is the Saga pattern?

The Saga pattern coordinates a business process that spans multiple services without holding one database transaction open across all of them.

A saga consists of local transactions. If a later step fails, the system runs compensating actions to undo or offset earlier work.

Example order flow:

  1. Order service creates a pending order.
  2. Inventory service reserves stock.
  3. Payment service charges the customer.
  4. Order service marks the order confirmed.

If payment fails after stock is reserved, a compensation can release the reservation and mark the order as failed.

Two common coordination styles are:

  • Choreography: services react to events from other services.
  • Orchestration: a coordinator tells each service what step to perform.

Sagas support distributed workflows but require careful handling of retries, duplicate messages, timeouts, and compensation failures. They do not provide the same all-or-nothing guarantee as one database transaction.


16. How do you handle production issues in React + Java?

I use a structured process:

  1. Assess impact: identify affected users, tenants, requests, and business operations. Check whether there is a safe mitigation.
  2. Gather evidence: check browser errors and network requests on the React side, and logs, traces, metrics, and dependency health on the Java side.
  3. Trace the request: use a correlation or trace ID to follow a request through the browser, API gateway, Java service, and downstream dependencies.
  4. Reproduce safely: use the reported steps and relevant environment details. Avoid changing production data simply to investigate.
  5. Find the cause: compare working and failing inputs, recent deployments, configuration or feature-flag changes, and downstream behavior.
  6. Mitigate and fix: use an approved rollback, configuration change, or code fix; validate it and monitor recovery.
  7. Follow up: document the cause, impact, fix, and actions that reduce the chance of recurrence.

Example: If an offer page fails to load, I would check the browser console and API response first. If the request returns an error, I would correlate its request ID with Java logs, validate the request and tenant configuration, and check the database or downstream service involved. Then I would confirm the fix against the failing scenario and monitor the affected flow.

The exact tools vary by organization. Avoid claiming a particular production tool or process unless you have actually used it.


📨 Kafka

17. What is Kafka?

Apache Kafka is a distributed event-streaming platform. Producers write records to topics; Kafka stores them in partitions; consumers read those records.

Example: when an order is placed, an order service can publish an OrderCreated event. Notification, analytics, and fulfillment services can each consume that event independently.

Kafka is useful for event-driven communication, data pipelines, and workloads that benefit from durable, scalable event storage. A consumer can read records at its own pace, and records can be retained for a configured period.


18. How does Kafka handle high throughput?

Kafka achieves high throughput through several design choices:

  • Partitions let data be written to and read from multiple logs in parallel.
  • Sequential disk writes are efficient for append-heavy workloads.
  • Batching lets producers send multiple records together.
  • Compression can reduce network and storage overhead.
  • Consumer groups let consumers process separate partitions in parallel.
  • Replication improves durability while distributing data across brokers.

Actual throughput depends on factors such as the number of partitions, message size, replication settings, producer configuration, hardware, network, and consumer processing speed.


19. What is replication in Kafka?

Replication keeps copies of a partition on multiple brokers. One replica is the leader for reads and writes; the others are followers that copy its data.

If the leader broker fails, Kafka can elect an in-sync follower as the new leader. Replication helps the system recover from broker failure, but it uses extra storage and network bandwidth.

Important settings include replication factor and minimum in-sync replicas. Stronger durability requirements can reduce the number of writes Kafka accepts when too few replicas are healthy.


20. What is an offset in Kafka?

An offset is a record’s position within a partition. It increases as records are written to that partition.

A consumer tracks the offsets it has processed so it can resume after restarting. Offsets belong to each partition, so a consumer group has separate progress for every partition it consumes.

For reliable processing, a consumer must choose when to commit offsets. Committing before processing finishes risks losing work if the consumer fails. Committing after processing can cause a record to be processed again if the consumer fails before the commit. Many applications therefore design consumers to be idempotent, so duplicate processing does not create incorrect results.


21. How do partitions and consumer groups help with Kafka scalability?

A topic’s partitions allow multiple consumers to process different parts of the topic concurrently. A consumer group coordinates consumers that share the work.

If a topic has six partitions and a group has three consumers, Kafka can assign roughly two partitions to each consumer. If the group has more consumers than partitions, some consumers will be idle for that topic.

Records with the same key are normally routed to the same partition, which preserves their order within that partition. That means partition-key choice affects both ordering and load distribution. A skewed key can send too much traffic to one partition.


🗄️ Database

22. How do you perform database query optimization?

I start by measuring the query and examining the database’s execution plan rather than guessing. A typical process is:

  1. Find the slow query and determine its frequency and data size.
  2. Use EXPLAIN or the database’s equivalent to see how it accesses data.
  3. Check whether filters and joins use appropriate indexes.
  4. Select only the columns needed; avoid retrieving full rows unnecessarily.
  5. Review joins, sorting, grouping, and subqueries for unnecessary work.
  6. Check for N+1 queries in application code.
  7. Consider pagination, batching, or schema changes if appropriate.
  8. Measure again with representative data and monitor the result.

An index can help a query locate rows faster, but indexes consume storage and can make inserts and updates more expensive. Query optimization is a balance, and the best choice depends on actual data and workload.


23. What is indexing?

An index is an additional data structure that helps a database locate rows without scanning the whole table. It is similar to an index at the back of a book: it helps find entries quickly, but takes space to maintain.

CREATE INDEX idx_customer_email ON customer(email);
Enter fullscreen mode Exit fullscreen mode

This can make lookups by email faster:

SELECT id, name
FROM customer
WHERE email = 'renuka@example.com';
Enter fullscreen mode Exit fullscreen mode

Indexes are most useful on columns commonly used in filters, joins, or sorting. Too many indexes add storage cost and slow writes because the database must update each relevant index when data changes. A composite index’s column order can also affect which queries it supports.


24. What is partitioning?

Partitioning divides a large table or dataset into smaller pieces while presenting them as one logical table. The database can direct a query to relevant partitions when its filter matches the partition key.

For example, an events table may be partitioned by month. A query for a date range in March may scan only March’s partition rather than every row.

Partitioning can help manage very large datasets, improve maintenance, and reduce the amount of data scanned for suitable queries. The partitioning strategy must fit the access pattern; a poor partition key can leave queries scanning many partitions or create uneven sizes.


25. Difference between indexing and partitioning

Indexing Partitioning
Creates a data structure to find rows faster Splits a table or dataset into smaller parts
Commonly improves lookups, joins, or sorting Commonly helps manage large tables and limit scans to relevant data
Can be applied to selected columns Uses a partitioning strategy, often based on a key such as date or region
Adds storage and write-maintenance overhead Adds schema and operational complexity; benefits depend on the query and partition key

They can be used together. For example, a date-partitioned table may also have indexes that help locate rows inside each partition.


⚛️ React

26. What is Context API?

The Context API lets a React component make a value available to descendants without passing it through every intermediate component as props.

import { createContext, useContext } from "react";

const ThemeContext = createContext("light");

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Save</button>;
}
Enter fullscreen mode Exit fullscreen mode

Context is useful for values shared by many components, such as a theme, current locale, or authenticated-user information. A context update can re-render components that consume it, so avoid putting frequently changing, large application state into one context without considering the render impact.


27. What is Redux?

Redux is a state-management library. It keeps application state in a store and updates it through actions and reducers.

In modern React applications, Redux Toolkit is the recommended Redux approach because it reduces the amount of setup code.

Conceptually:

  1. A component dispatches an action, such as addItem.
  2. A reducer calculates the next state.
  3. The store holds the updated state.
  4. Components that select that state update when it changes.

Redux is useful when many parts of an application need shared state, when state transitions need to be predictable, or when debugging and middleware support are valuable. It is not required for every React application; component state and context may be enough for simpler needs.


28. Difference between Context API and Redux

Context API Redux
Built into React Separate state-management library
Provides values through a component tree Provides a centralized store with actions and reducers
Useful for relatively simple shared values Useful for complex shared state and explicit state transitions
Does not define a full state-management workflow on its own Includes conventions and tooling for updates, middleware, and debugging

They are not direct substitutes in every case. Context handles value distribution; Redux provides a broader pattern for managing application state. Choose based on complexity and team needs.


29. What is an HOC (Higher Order Component)?

A Higher Order Component is a function that accepts a component and returns a new component with additional behavior or props.

function withLoading(Component) {
  return function WrappedComponent({ loading, ...props }) {
    if (loading) return <p>Loading...</p>;
    return <Component {...props} />;
  };
}

const CustomerListWithLoading = withLoading(CustomerList);
Enter fullscreen mode Exit fullscreen mode

HOCs were commonly used to reuse behavior such as authentication checks, loading states, or data subscriptions. They can still be useful when working with code designed around them, but they can create nested component wrappers and make data flow harder to follow.


30. What are Custom Hooks?

A custom Hook is a JavaScript function whose name starts with use and that can call React Hooks. It packages reusable stateful behavior for use across components.

import { useEffect, useState } from "react";

function useCustomer(customerId) {
  const [customer, setCustomer] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let active = true;

    fetch(`/api/customers/${customerId}`)
      .then(response => response.json())
      .then(data => {
        if (active) setCustomer(data);
      })
      .finally(() => {
        if (active) setLoading(false);
      });

    return () => {
      active = false;
    };
  }, [customerId]);

  return { customer, loading };
}
Enter fullscreen mode Exit fullscreen mode

A component can call useCustomer(id) to reuse that behavior. Custom Hooks share logic, not one shared instance of state: each component calling the Hook has its own Hook state unless it connects to shared state elsewhere.

In production code, also handle non-success HTTP responses and cancellation appropriately.


31. Difference between HOC and Custom Hooks

HOC Custom Hook
Wraps a component and returns another component Is called from a component or another Hook
Reuses behavior by composing component wrappers Reuses stateful logic directly
Can add props or control rendering Returns values and functions to the caller
May create nested wrapper structures Keeps behavior closer to the component using it

For new function components, custom Hooks are often a straightforward way to share behavior. HOCs remain useful where an existing codebase or library uses the pattern.


32. What is Lazy Loading in React?

Lazy loading delays loading a component’s code until the application needs it. This can reduce the initial JavaScript bundle size.

import { lazy, Suspense } from "react";

const ReportsPage = lazy(() => import("./ReportsPage"));

function App() {
  return (
    <Suspense fallback={<p>Loading reports...</p>}>
      <ReportsPage />
    </Suspense>
  );
}
Enter fullscreen mode Exit fullscreen mode

The bundler can create a separate chunk for ReportsPage, which is fetched when the component is rendered. Lazy loading is often useful for routes or large, infrequently used features. Add a loading fallback, and handle chunk-load failures if the application needs robust recovery.


33. How do you call two APIs concurrently in React?

If two requests are independent, start both before waiting for either. With fetch, Promise.all() is a common approach:

async function loadPageData() {
  const [customerResponse, ordersResponse] = await Promise.all([
    fetch("/api/customer/42"),
    fetch("/api/customer/42/orders")
  ]);

  if (!customerResponse.ok || !ordersResponse.ok) {
    throw new Error("Failed to load page data");
  }

  const [customer, orders] = await Promise.all([
    customerResponse.json(),
    ordersResponse.json()
  ]);

  return { customer, orders };
}
Enter fullscreen mode Exit fullscreen mode

In a component, call this from an effect or through a data-fetching library, and represent loading and error states in the UI. If one request depends on the result of another, call them sequentially instead. Promise.all() rejects as soon as any input promise rejects, so use Promise.allSettled() when each result should be handled independently.


34. What is Promise.all()?

Promise.all() takes an iterable of promises and returns a promise that:

  • Fulfills when all input promises fulfill, with results in input order.
  • Rejects as soon as one input promise rejects.
const [profile, settings] = await Promise.all([
  fetchProfile(),
  fetchSettings()
]);
Enter fullscreen mode Exit fullscreen mode

This is useful for independent tasks when all results are required. It does not automatically cancel other operations if one rejects.


35. What are the other Promise methods?

Method Behavior Example use
Promise.all() Fulfills when all fulfill; rejects on the first rejection Several required independent API calls
Promise.allSettled() Waits for all to finish, whether fulfilled or rejected Display successful results while reporting failed ones
Promise.race() Settles as soon as the first input settles, whether success or failure Race a request against a timeout
Promise.any() Fulfills with the first successful result; rejects only if all reject Try multiple equivalent sources and use the first success

Example using allSettled():

const results = await Promise.allSettled([
  fetchProfile(),
  fetchRecommendations()
]);

for (const result of results) {
  if (result.status === "fulfilled") {
    console.log("Success:", result.value);
  } else {
    console.error("Failed:", result.reason);
  }
}
Enter fullscreen mode Exit fullscreen mode

🔀 Git, Jira and CI/CD

36. Explain Git branching strategy.

A Git branching strategy defines how teams create branches, collaborate, and merge changes.

A common feature-branch workflow is:

  1. Start a short-lived branch from the team’s integration branch.
  2. Make a focused change and commit it.
  3. Open a pull request for review.
  4. Run automated checks.
  5. Merge after review and required checks pass.
  6. Release from the agreed release or main branch.

A team may use branch names such as feature/PROJ-123-add-customer-search or bugfix/PROJ-456-fix-validation. The exact names and branch structure depend on the team’s workflow.

Short-lived branches reduce the time changes remain separate, which can reduce merge conflicts. Teams should agree on how releases, hotfixes, and unfinished work are handled.


37. How do you maintain branching control?

Branch control comes from a combination of team conventions and repository settings:

  • Protect important branches such as main or develop.
  • Require pull requests instead of direct pushes.
  • Require approvals and successful CI checks before merging.
  • Restrict who can merge or push to release branches.
  • Use clear branch names connected to work items.
  • Keep branches focused and merge regularly.
  • Delete merged branches when they are no longer needed.
  • Use tags or release branches according to the team’s release process.

The aim is to keep changes reviewable and ensure that code reaches important branches only after the team’s required checks.


38. How do you integrate Git with Jira?

Teams commonly associate Git changes with Jira work by including the Jira issue key in branch names, commit messages, and pull-request titles.

Example:

Branch: feature/PROJ-123-customer-search
Commit: PROJ-123 Add customer search endpoint
PR: PROJ-123 Implement customer search
Enter fullscreen mode Exit fullscreen mode

If the Git hosting system is connected to Jira, Jira can show related branches, commits, and pull requests on the issue. Some teams also use commit conventions or automation to update issue status, but those behaviors depend on repository and Jira configuration.


39. What is a CI/CD pipeline?

A CI/CD pipeline automates steps that help build, test, and deliver software.

  • Continuous Integration (CI): frequently integrate changes and automatically build and test them.
  • Continuous Delivery: keep software ready to release, with a deployment step that may require approval.
  • Continuous Deployment: automatically deploy a change to production after it passes the required checks.

A pipeline might run on every pull request and again after merge. Its exact stages depend on the application and organization.


40. Explain the typical CI/CD flow from code commit to deployment.

A typical flow is:

  1. A developer pushes a branch or opens a pull request.
  2. The pipeline checks out the code and restores dependencies.
  3. It runs formatting, static analysis, and security checks.
  4. It compiles or builds the frontend and backend.
  5. It runs unit, integration, and possibly end-to-end tests.
  6. After approval and successful checks, the change is merged.
  7. The pipeline creates a versioned artifact, such as a container image.
  8. The artifact is deployed to a test or staging environment.
  9. Smoke or acceptance checks validate the deployment.
  10. The artifact is promoted to production, automatically or with approval.
  11. Monitoring checks the application; a rollback or recovery plan is used if the release fails.

Deploying the same built artifact through environments helps avoid differences between what was tested and what was released.


💻 Coding Questions

41. Given a HashMap<String, Integer>, find the top 3 keys based on their associated values using Java Streams.

import java.util.*;
import java.util.stream.Collectors;

public class TopKeysExample {
    public static List<String> topThreeKeys(Map<String, Integer> scores) {
        return scores.entrySet()
            .stream()
            .sorted(Map.Entry.<String, Integer>comparingByValue()
                .reversed()
                .thenComparing(Map.Entry.comparingByKey()))
            .limit(3)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 92);
        scores.put("Bob", 85);
        scores.put("Chitra", 98);
        scores.put("David", 92);

        System.out.println(topThreeKeys(scores));
        // [Chitra, Alice, David]
    }
}
Enter fullscreen mode Exit fullscreen mode

The entries are sorted by value in descending order. thenComparing(...) provides a deterministic alphabetical ordering when two values are equal. The stream is limited to three entries, and each entry is mapped to its key.

This returns up to three keys if the map contains fewer than three entries. If you need the values too, return the entries or collect them into a new map.


42. How does HashMap.put(key, value) work when the key is a String?

At a high level, HashMap.put(key, value):

  1. Computes a hash from the key. For a String, hashCode() is based on its character contents.
  2. Uses the hash to find a bucket.
  3. Checks entries in that bucket to see whether one has an equal key.
  4. If an equal key exists, replaces its value.
  5. Otherwise, adds a new entry.

String is immutable, which makes it a suitable map key: its contents and hash do not change after insertion.

Map<String, Integer> counts = new HashMap<>();

counts.put("Java", 1);
counts.put("Java", 2);

System.out.println(counts.get("Java")); // 2
Enter fullscreen mode Exit fullscreen mode

The second call updates the value because the new "Java" string compares equal to the existing key. HashMap uses both hash information and key equality to identify a matching key; different keys can still have the same hash, so equality checking matters.


43. What is the time complexity of sorting map entries to find the top 3?

If a map has n entries, sorting all entries usually takes O(n log n) time and O(n) additional space for the stream’s sorted result or working data.

For only the top k values, a bounded min-heap can reduce the work to approximately O(n log k) time and O(k) extra space. For k = 3, that is effectively linear in the number of entries.

For an interview answer: sorting is simpler and readable when the map is moderate in size or the full order is useful; a heap is more efficient when the map is large and only a few top results are needed.


🔥 High-priority revision topics

For a 4.2-year Java + Spring Boot + React full-stack interview, make sure you can explain these with examples from work or projects:

  1. Java Streams and functional interfaces: filtering, mapping, collecting, and when streams improve readability.
  2. Collections and HashMap: equality and hashing, collisions, and common operation complexity.
  3. Concurrency: thread pools, CompletableFuture, synchronization, and limiting concurrent work.
  4. Spring REST APIs: validation, DTOs, status codes, service layers, and centralized exception handling.
  5. Microservices resilience: timeouts, retries, circuit breakers, and fallbacks.
  6. Kafka: topics, partitions, consumer groups, offsets, replication, and duplicate-safe processing.
  7. Database performance: execution plans, indexes, query shape, and when partitioning is useful.
  8. React: Hooks, state ownership, Context, Redux, and code splitting.
  9. Promises: concurrent calls, Promise.all(), and partial-failure handling with Promise.allSettled().
  10. Git and CI/CD: pull requests, branch protections, automated checks, artifact promotion, and rollback.
  11. Production debugging: establish impact, trace the request, inspect evidence, mitigate, fix, and monitor.

Conclusion
These are the questions I was asked during my Kaiser interview, along with answers and examples to help explain the concepts. I hope they’re useful as you prepare for interviews covering Java, Spring Boot, Kafka, React and full-stack development.
If you’ve been asked similar questions, or have other topics you’re preparing for, share them in the comments.

Top comments (1)

Collapse
 
bagombeka_job profile image
Bagombeka Job •

Hey Patil,
Thank you for sharing this.
I have some upcoming interview this week and this is really going to be very helpful.