When building a React application, we often need to communicate with a backend server or an API. For example, an e-commerce application may need to fetch products, add new products, update product information, or delete products. To perform these operations, we need to send HTTP requests. Axios is a popular JavaScript library used for this purpose.
Axios is a promise-based HTTP client that allows frontend applications to communicate with APIs and backend servers. We can use Axios with JavaScript, React, Node.js, and other JavaScript-based applications.
First, we need to install Axios in our React project:
npm install axios
Then we import it into our component:
import axios from "axios";
Axios supports different HTTP methods. The most commonly used methods are GET, POST, PUT, PATCH, and DELETE.
A PUT or PATCH request is used to update existing data, while DELETE is used to remove data:
axios.delete("/products/1");
Axios is commonly used with React's useEffect hook when we need to fetch data when a component loads. We can also use async/await with Axios to make asynchronous API calls easier to understand. Since API requests can fail, we normally use try...catch for error handling.
The easiest way to remember Axios is to think of it as a messenger between React and the backend.
React ↓ Axios ↓ API / Server ↓ Database ↓ API / Server ↓ Axios ↓ React
Indie developers and small teams ship code faster than they write about it. Herald automates the generation of blog posts and social copy from repo events, but the real value lies in knowing which pieces actually reach an audience. Without visibility into read counts and engagement, a developer cannot optimize the content calendar or justify the time spent on marketing automation.
Solution Overview
Herald adds Umami analytics directly into the published posts. The integration captures page views, referrer data, and user interaction without sending data to third‑party services. By embedding a lightweight script with the correct website ID, every time a reader visits a Dev.to or Medium article that Herald published, the event is recorded and surfaced in the Herald dashboard. This gives developers a single source of truth for marketing automation metrics.
How It Works
Herald is built on FastAPI with SQLAlchemy for persistence, Celery for background tasks, Redis for task queues, and a React front‑end styled with Tailwind CSS. The analytics pipeline is split between the back‑end and the front‑end.
1. Back‑end: Exposing the Umami Endpoint
The FastAPI application exposes a small endpoint that the front‑end calls when a page loads. The endpoint simply records the event in a PostgreSQL table.
# app/api/analytics.py from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from app.db import get_db from app.models import PageView
The PageView model stores the page URL, user agent, timestamp, and a reference to the content ID. No sensitive data is logged.
2. Front‑end: Injecting the Umami Script
In the React component that renders a published article, we inject the Umami script once per page load. The script tag is added only if the UMAMI_WEBSITE_ID environment variable is set.
// components/Article.tsx import { useEffect } from "react";
The script automatically reports page views to the Umami backend, which in turn forwards the data to the Herald analytics endpoint.
3. Celery Task: Aggregating Metrics
Once a view is recorded, a Celery worker processes the event to update aggregate metrics used in the content calendar. The task runs every minute, batching view records to reduce database load.
# app/tasks/analytics.py from celery import shared_task from app.db import get_session from app.models import PageView, Content
@shared_task def aggregate_views(): session = get_session() views = session.query(PageView).filter(PageView.processed == False).all() for v in views: content = session.query(Content).filter_by(url=v.page).first() if content: content.views += 1 v.processed = True session.commit()
The aggregated view count is displayed in the Herald UI next to each content item, allowing developers to see which AI‑generated posts performed best.
Using the Feature
Enable Umami – Set UMAMI_WEBSITE_ID in the Heroku / Fly.io config.
Deploy – The front‑end automatically injects the script; no changes to the Dev.to or Medium publishing logic are required.
View Dashboard – In the Herald UI, open the content calendar. Each row now shows a Views column populated from the Umami data.
Iterate – Use the view counts to adjust the content calendar and prioritize topics that resonate with readers.
The integration is optional; developers who prefer not to use Umami simply omit the environment variable, and the feature falls back to the default analytics path.
Benefits
No third‑party tracking – All data stays within the Herald stack, respecting privacy.
Real‑time insights – The Celery aggregation runs every minute, giving near‑real‑time metrics.
Seamless developer experience – The integration requires only a single environment variable and a few lines of code.
Data‑driven marketing automation – By tying view counts to AI‑generated content, developers can refine their content calendar and improve engagement.
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
Here, Function<String, Integer> accepts a String and returns an Integer. The lambda implements its single abstract method, apply.
@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.
MethodPurposeExamplelength()Gets the number of characters"Java".length() → 4isEmpty()Checks whether length is zero"".isEmpty() → trueisBlank()Checks whether the string is empty or whitespace (Java 11+)" ".isBlank() → truecharAt(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") → trueequals(other)Compares contents, case-sensitive"java".equals("Java") → falseequalsIgnoreCase(other)Compares contents, ignoring case"java".equalsIgnoreCase("Java") → truecompareTo(other)Lexicographically compares stringsUseful for sortingstartsWith(prefix) / endsWith(suffix)Checks the beginning or end"file.csv".endsWith(".csv")indexOf(text) / lastIndexOf(text)Finds a character or substring"banana".indexOf("a") → 1toUpperCase() / 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 stringString.join("-", "a", "b") → "a-b"format(...)Formats a stringString.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
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.
executor.submit(() -> { System.out.println("Task running on " + Thread.currentThread().getName()); });
executor.shutdown();
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.
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.
ExecutorServiceCompletableFutureManages and runs submitted tasks on threadsRepresents an eventual result and lets you compose worksubmit() returns a FutureOffers chaining and combining methods such as thenApply() and thenCombine()Useful for controlling a pool and scheduling tasksUseful for expressing asynchronous workflowsA basic Future does not provide convenient continuation chainingSupports 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.
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();
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:
Prefer stream reductions that combine results safely:
int total = numbers.parallelStream() .mapToInt(Integer::intValue) .sum();
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(); } }
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:
A controller for HTTP routing and request/response handling.
@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) {}
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 {}
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. } }
@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:
A service instance starts.
It registers its address and health status with a registry, or an infrastructure platform tracks it.
A client asks for instances of a service.
The client or a load balancer chooses an available instance.
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:
Closed: requests pass through; failures are counted.
Open: requests are rejected or sent to a fallback without calling the dependency.
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:
Order service creates a pending order.
Inventory service reserves stock.
Payment service charges the customer.
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:
Assess impact: identify affected users, tenants, requests, and business operations. Check whether there is a safe mitigation.
Gather evidence: check browser errors and network requests on the React side, and logs, traces, metrics, and dependency health on the Java side.
Trace the request: use a correlation or trace ID to follow a request through the browser, API gateway, Java service, and downstream dependencies.
Reproduce safely: use the reported steps and relevant environment details. Avoid changing production data simply to investigate.
Find the cause: compare working and failing inputs, recent deployments, configuration or feature-flag changes, and downstream behavior.
Mitigate and fix: use an approved rollback, configuration change, or code fix; validate it and monitor recovery.
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:
Find the slow query and determine its frequency and data size.
Use EXPLAIN or the database’s equivalent to see how it accesses data.
Check whether filters and joins use appropriate indexes.
Select only the columns needed; avoid retrieving full rows unnecessarily.
Review joins, sorting, grouping, and subqueries for unnecessary work.
Check for N+1 queries in application code.
Consider pagination, batching, or schema changes if appropriate.
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);
This can make lookups by email faster:
SELECT id, name FROM customer WHERE email = 'renuka@example.com';
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
IndexingPartitioningCreates a data structure to find rows fasterSplits a table or dataset into smaller partsCommonly improves lookups, joins, or sortingCommonly helps manage large tables and limit scans to relevant dataCan be applied to selected columnsUses a partitioning strategy, often based on a key such as date or regionAdds storage and write-maintenance overheadAdds 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";
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:
A component dispatches an action, such as addItem.
A reducer calculates the next state.
The store holds the updated state.
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 APIReduxBuilt into ReactSeparate state-management libraryProvides values through a component treeProvides a centralized store with actions and reducersUseful for relatively simple shared valuesUseful for complex shared state and explicit state transitionsDoes not define a full state-management workflow on its ownIncludes 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} />; }; }
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.
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 }; }
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
HOCCustom HookWraps a component and returns another componentIs called from a component or another HookReuses behavior by composing component wrappersReuses stateful logic directlyCan add props or control renderingReturns values and functions to the callerMay create nested wrapper structuresKeeps 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.
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:
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.
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?
MethodBehaviorExample usePromise.all()Fulfills when all fulfill; rejects on the first rejectionSeveral required independent API callsPromise.allSettled()Waits for all to finish, whether fulfilled or rejectedDisplay successful results while reporting failed onesPromise.race()Settles as soon as the first input settles, whether success or failureRace a request against a timeoutPromise.any()Fulfills with the first successful result; rejects only if all rejectTry multiple equivalent sources and use the first success
for (const result of results) { if (result.status === "fulfilled") { console.log("Success:", result.value); } else { console.error("Failed:", result.reason); } }
🔀 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:
Start a short-lived branch from the team’s integration branch.
Make a focused change and commit it.
Open a pull request for review.
Run automated checks.
Merge after review and required checks pass.
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.
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:
A developer pushes a branch or opens a pull request.
The pipeline checks out the code and restores dependencies.
It runs formatting, static analysis, and security checks.
It compiles or builds the frontend and backend.
It runs unit, integration, and possibly end-to-end tests.
After approval and successful checks, the change is merged.
The pipeline creates a versioned artifact, such as a container image.
The artifact is deployed to a test or staging environment.
Smoke or acceptance checks validate the deployment.
The artifact is promoted to production, automatically or with approval.
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.
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):
Computes a hash from the key. For a String, hashCode() is based on its character contents.
Uses the hash to find a bucket.
Checks entries in that bucket to see whether one has an equal key.
If an equal key exists, replaces its value.
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
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:
Java Streams and functional interfaces: filtering, mapping, collecting, and when streams improve readability.
Collections and HashMap: equality and hashing, collisions, and common operation complexity.
Concurrency: thread pools, CompletableFuture, synchronization, and limiting concurrent work.
Spring REST APIs: validation, DTOs, status codes, service layers, and centralized exception handling.
Microservices resilience: timeouts, retries, circuit breakers, and fallbacks.
Kafka: topics, partitions, consumer groups, offsets, replication, and duplicate-safe processing.
Database performance: execution plans, indexes, query shape, and when partitioning is useful.
React: Hooks, state ownership, Context, Redux, and code splitting.
Promises: concurrent calls, Promise.all(), and partial-failure handling with Promise.allSettled().
Git and CI/CD: pull requests, branch protections, automated checks, artifact promotion, and rollback.
Production debugging: establish impact, trace the request, inspect evidence, mitigate, fix, and monitor.