ai

AI Interview interview questions

The usual approach is to expose a plain REST endpoint that accepts the input the model needs and returns its prediction as JSON, the same shape as any other Spring Boot controller. If the model is written in a Java-compatible format, it can be loaded directly into the application and run inference in the same process; otherwise the typical pattern is calling out to a separate model-serving process over the network, keeping the heavy machine learning runtime isolated from the main application.

Beyond the basic endpoint, production-grade serving adds several layers: validate incoming input so malformed requests fail fast with a clear error instead of confusing the model, batch multiple requests together where possible to use the hardware more efficiently, set a timeout with a sensible fallback for calls to an external model service so one slow request does not stall everything behind it, and cache results for repeated identical inputs. Monitoring should track latency percentiles such as the 95th or 99th, not just the average, because a small number of very slow requests is what users actually notice.

Spring Boot

A model can be converted into a browser-friendly format and loaded using a library such as TensorFlow.js, which runs inference directly in JavaScript, optionally accelerated by the device's GPU through WebGL. This avoids a round trip to a server for every prediction and keeps user data on the device, though it is generally limited to smaller models, since browser memory and compute are far more constrained than a dedicated server.

JavaScript

Discuss request batching, async processing with queues, horizontal scaling of stateless inference workers, model caching, and back-pressure handling. Mention monitoring latency percentiles and cost trade-offs.

Spring Boot

C++ gives a programmer extremely fine grained control over memory layout, avoids the overhead of automatic garbage collection, and compiles directly down to efficient native machine code rather than running inside an interpreted or heavily managed runtime. For inference workloads, where the exact same numerical operations run an enormous number of times over large amounts of data, even small inefficiencies get multiplied dramatically, so the ability to squeeze out predictable, low overhead performance is exactly why so many production grade inference engines are written in C++.

C++

NumPy stores numerical data in contiguous blocks of memory using a fixed, uniform type, and performs operations on entire arrays at once using optimized, compiled code under the hood, a technique known as vectorization. A plain Python list stores generic objects scattered in memory and requires the interpreter to loop over them one at a time in slow, interpreted bytecode. Rewriting a loop-based numerical calculation as a vectorized NumPy operation commonly speeds it up by one or two orders of magnitude.

Python

Common approaches include processing the data in smaller chunks rather than loading it all at once, using a library designed for out-of-core or distributed computation that can operate on data larger than available memory, or downsampling the data during initial exploration and only working with the full dataset for a final training run. Storing data in an efficient columnar format on disk also reduces how much needs to be read for any given operation.

Python

Modern processors read memory in contiguous blocks called cache lines, and operations on data that is laid out contiguously in memory run dramatically faster than operations on data scattered across many different locations, because far fewer slow trips back to main memory are needed. Tensor operations typically process enormous numbers of values in tight, repeated loops, so arranging that data in a cache friendly, contiguous layout, and accessing it in a predictable order, can make an enormous practical difference to overall throughput compared to a naive, scattered layout.

C++

Start by keeping the serving layer stateless, meaning any instance can handle any request without needing data left over from a previous one, since this is what allows scaling horizontally by simply adding more instances behind a load balancer as traffic grows. From there, batching incoming requests together before sending them to the model makes far more efficient use of accelerator hardware such as GPUs than handling requests one at a time.

A queue in front of the inference workers absorbs sudden traffic spikes instead of overwhelming them directly, and applying back-pressure, meaning rejecting or delaying new requests once the queue grows too long, protects the system from collapsing under overload rather than serving everyone slowly. Caching frequent or repeated inputs avoids recomputation entirely for the cases where it applies. Throughout all of this, tail latency, the slowest handful of requests out of every hundred or thousand, matters more than the average, since average latency can look perfectly healthy while a meaningful share of real users experience a slow, frustrating response.

Spring Boot

Typically the Node server holds the API key securely and exposes its own endpoint that the front end calls, which then forwards the request to the AI provider's API using a library like fetch or an official SDK, and returns the result back to the client. Keeping the key on the server rather than in front-end code prevents it from being exposed to anyone inspecting the browser's network requests.

JavaScript

A typical approach wraps the model behind a lightweight web framework such as FastAPI or Flask, exposing an endpoint that accepts input, runs the loaded model, and returns a prediction as a response. For higher-throughput needs, a dedicated model-serving tool can handle batching, versioning and hardware acceleration more efficiently than a hand-rolled endpoint. Either way, the serving code is usually kept separate from the training code, since the two have very different performance and reliability requirements.

Python

A general purpose memory allocator has to handle every possible allocation pattern safely and correctly, which introduces bookkeeping overhead that becomes noticeable once allocations happen extremely frequently, as they often do inside a tight inference loop. A custom allocator tailored to one specific, predictable pattern, such as always allocating fixed size blocks of the same shape, can skip much of that general purpose bookkeeping entirely, reduce fragmentation, and reuse memory far more quickly, often producing a meaningful, measurable speed improvement in that one specific hot path.

C++

Training is the process of building a model from historical data, and it is a heavy, resource-intensive job that typically runs occasionally, as a batch process that might take hours or days, often on specialised hardware. Inference is the process of using an already-trained model to answer a new request, and it needs to be fast, predictable and available essentially all the time, since a user or another service is waiting on the response.

Because these two workloads have such different characteristics, one occasional and heavy, the other constant and latency-sensitive, they are usually run on separate infrastructure. Training pipelines can be scheduled and scaled up temporarily without affecting anything else, while the inference path is optimised specifically for low latency and high availability, often with its own scaling rules, caching and monitoring distinct from the training system. Mixing the two on the same infrastructure tends to make both slower and harder to operate reliably.

Spring Boot

Many AI providers support a streaming response format, where the answer arrives in small chunks as it is generated rather than all at once. The Node server can read this stream and forward each chunk to the browser as it arrives, commonly using server-sent events or a chunked HTTP response, letting the front end display words appearing progressively instead of a long silent wait followed by the full answer.

JavaScript

The general strategy is to add guardrails around the model rather than trusting its raw output directly. On the way in, validate and clean inputs so obviously malformed or malicious requests never reach the model. On the way out, check the output against a set of rules or a separate safety classifier before it reaches the user, and fall back to a safe, generic response whenever confidence in the result is low or a check fails.

Just as important is what happens after deployment: log both inputs and outputs so problematic cases can be reviewed later, give users an easy way to report a bad result, and monitor output quality continuously rather than assuming a model that tested well stays reliable forever. Real-world inputs drift over time, so a guardrail and monitoring system that catches degradation early is what actually keeps an AI feature safe in production, not just careful testing before launch.

Spring Boot

Frequent culprits include row-by-row looping over a pandas DataFrame instead of using vectorized operations, repeatedly recomputing the same expensive transformation instead of caching it, and reading data from disk in an inefficient format or in many small pieces. Addressing these usually means rewriting loops as vectorized pandas or NumPy operations, caching intermediate results that do not change between runs, and switching to a more efficient file format designed for fast, columnar reads.

Python

SIMD, short for single instruction multiple data, refers to special processor instructions capable of performing the very same arithmetic operation on several values simultaneously in a single step, rather than processing one value at a time through an ordinary loop. For the repetitive, uniform numerical operations common in machine learning, such as adding or multiplying large arrays of numbers together, using SIMD instructions can process many values per processor cycle, delivering a substantial performance improvement over an equivalent naive loop for the exact same underlying arithmetic.

C++

Since JavaScript in the browser runs on a single main thread, a slow request should always be made asynchronously with fetch and await rather than any blocking call, which does not exist for network requests anyway. Showing a loading indicator, disabling the relevant button, and letting the rest of the page remain responsive while the request is in flight keeps the experience usable even when the AI response takes several seconds.

JavaScript

The general strategy is to allocate any buffers that will be reused up front, before the loop even begins, and then simply overwrite their contents on each pass through the loop rather than allocating fresh memory every single iteration. Reusing a fixed pool of pre allocated buffers, avoiding unnecessary temporary objects, and preferring stack based storage over heap allocation wherever the size is genuinely known ahead of time all help keep the loop's body doing pure computation instead of spending time repeatedly requesting and releasing memory from the system.

C++

Identical or very similar prompts can be cached, keyed by a hash of the request, using something like an in-memory cache for a single server instance or a shared cache like Redis across multiple servers. Caching is especially effective for AI features where many users ask the same common questions, since it avoids paying for and waiting on a fresh generation every single time.

JavaScript

This situation is most commonly caused by data drift, where the real inputs a model sees in production gradually differ from the data it was trained and tested on, so its learned patterns stop matching reality as well as they once did. The first useful step is comparing the statistical distribution of live inputs against the original training data to confirm drift is actually the cause rather than a bug in the serving code.

Once drift is confirmed, add ongoing monitoring on prediction quality itself, not just system metrics like latency, and build a feedback loop that collects freshly labelled examples from real usage. Retraining or fine-tuning the model on this recent data usually restores performance closer to what was seen in testing. Before fully switching traffic to a retrained version, a shadow deployment, where the new model receives a copy of live traffic and its predictions are logged but not actually used, allows improvement to be validated safely without risking user-facing quality in the meantime.

Spring Boot

A GPU contains thousands of simpler cores well suited to performing the same operation across huge amounts of data simultaneously, which matches how most deep learning training works, giving it a dramatic speed advantage for that kind of workload. A CPU has far fewer, more general-purpose cores better suited to varied, sequential logic. In practice, GPUs are used heavily for training large models, while CPUs remain perfectly adequate, and often more cost-effective, for lighter inference workloads or classical machine learning models.

Python

The common approach is to write a thin binding layer, often using a tool such as pybind11, that exposes selected C++ functions and classes to Python as if they were ordinary Python objects and functions. Python code can then call directly into the fast, compiled C++ implementation for the actual heavy computation, while still enjoying Python's convenience and its enormous ecosystem for everything surrounding that computation, such as data loading, visualization, and general scripting, effectively getting the strengths of both languages in one system.

C++

Data leakage happens when information from outside the training data, particularly from the test or validation set, inadvertently influences the model during training, making its performance during evaluation look better than it will actually be in the real world. A common accidental cause is applying a transformation such as scaling or filling missing values to the entire dataset before splitting it into training and test sets, letting statistics from the test data quietly leak into the training process.

Python

A rate limiter, either a simple token bucket implemented in memory or a shared one backed by Redis for a multi-server setup, can track how many requests a user or the whole application has made in a given time window and reject or queue requests once a limit is reached. This protects against runaway costs, respects the AI provider's own rate limits, and helps ensure fair usage across all users.

JavaScript

A common, effective pattern is to split incoming requests into independent batches and process each batch on a separate worker thread, ensuring that the actual model weights being read are treated as read only and shared safely across threads without ever needing a lock, since read only shared data causes no data races on its own. Any shared mutable state, such as a results queue collecting completed predictions, does need proper synchronization, typically through a mutex or a lock free queue, to avoid race conditions between the worker threads.

C++

Processing requests one at a time, especially on a GPU, wastes a lot of the hardware's parallel capacity, since a single small request may not fully use the available compute. Grouping several requests into one batch lets the same amount of computation serve many inputs at once, significantly increasing overall throughput, at the cost of a small amount of added latency while requests wait briefly to be grouped together before running.

Python

Documents are first converted into vector embeddings using an embedding model and stored in a vector database or a simple in-memory index. When a user asks a question, that question is also embedded, the most similar stored documents are retrieved by comparing vectors, and those retrieved snippets are included alongside the question in the prompt sent to the language model, grounding its answer in the actual source material rather than relying purely on its training data.

JavaScript

Without clear versioning, it becomes very difficult to reproduce a past result, understand exactly why a model behaves differently after a change, or roll back to a previous version if a new model performs worse in production. Versioning the dataset alongside the model ensures that a given model can always be tied back to the exact data it was trained on, which matters enormously when diagnosing unexpected behaviour or meeting requirements for auditability.

Python

Single precision floating point numbers use half the memory of double precision ones and can typically be processed roughly twice as fast, particularly when combined with hardware acceleration and SIMD style instructions common in machine learning workloads. Double precision offers noticeably more numerical accuracy, but that extra precision is rarely necessary for most neural network computations, which tend to remain robust to the small rounding errors introduced by single precision. As a result, single precision is the practical default for the vast majority of real world machine learning inference and training work.

C++

A WebSocket keeps a persistent, two-way connection open, which suits scenarios needing frequent back-and-forth updates, such as a live collaborative AI assistant or real-time status updates during a long-running generation. For a single request and a single response, even a streamed one, a plain HTTP request is usually simpler to implement and sufficient, reserving WebSockets for cases where the server also needs to push unsolicited updates to the client.

JavaScript

Running inference on the client, such as in the browser with TensorFlow.js, avoids server costs and keeps data local, but is limited to smaller models and depends on the user's device capability. Running inference on the server allows much larger, more capable models and centralized control over versioning and monitoring, but adds network latency, ongoing infrastructure cost, and requires careful handling of user data sent to the server.

JavaScript

Training code is typically run occasionally, can tolerate being slower, and often needs access to large historical datasets and experimentation tools. Inference code needs to be fast, reliable and lightweight, since it runs continuously in production and directly affects user-facing latency. Mixing the two together makes both harder to maintain, and can accidentally introduce training-only dependencies or slow paths into the exact code that needs to respond quickly to real requests.

Python

A typical approach starts with a dedicated profiling tool that samples the running program to reveal exactly where it is actually spending its time, function by function, rather than guessing based on intuition alone. From there, common areas worth investigating closely include unnecessary memory allocations occurring inside hot loops, cache unfriendly memory access patterns that force frequent slow trips to main memory, and any unexpectedly expensive function calls hiding inside what looks like a simple loop on the surface. Fixing the single largest bottleneck first, then measuring again, is generally far more effective than optimizing everything speculatively all at once.

C++

Wrapping calls with a retry strategy that uses backoff between attempts handles transient failures, while a circuit breaker can temporarily stop sending requests to a provider that is clearly failing repeatedly, avoiding piling up more failing requests. A sensible timeout, a fallback response or cached result for when the provider is unavailable, and clear logging of failures all help keep the rest of the application stable even when the AI dependency is not.

JavaScript

The general approach mirrors profiling any other Python code: use a tool such as cProfile or a line-by-line profiler to measure where time is actually being spent, rather than assuming which step is slow. For data pipelines specifically, it is also worth checking whether pandas operations are being applied in a vectorized way or looped over row by row, and whether time is dominated by actual computation or simply by reading and writing data to disk.

Python

The general approach involves first shrinking the model itself through techniques such as quantization, which represents its numbers using fewer bits, or pruning, which removes parts of the model that contribute very little to its final output, reducing both its memory footprint and how much computation it requires. From there, a lightweight, minimal runtime specifically designed for constrained devices, rather than a full featured, general purpose framework, is used to actually load and run that shrunken model, since a full desktop scale framework would likely be far too large and too slow to fit comfortably on such limited hardware.

C++

If the API supports streaming, chunks of the response can be displayed as they arrive, which is the smoothest experience for something like a chat response. When streaming is not available, a progress indicator, an estimated time remaining, or intermediate status messages such as "analyzing your request" give the user useful feedback and reduce the perceived wait, even though the actual result only appears once the whole operation finishes.

JavaScript

pandas can be slow when a row-wise loop or an apply call with a custom Python function is used instead of a built-in vectorized operation, since each row then has to pass through the Python interpreter individually rather than being processed in bulk by optimized internal code. Rewriting such logic using pandas' own vectorized methods, or falling back to underlying NumPy operations directly, typically produces a large speedup without changing what the code actually computes.

Python

Allocating and releasing memory on the heap has variable, somewhat unpredictable timing, since the underlying memory manager has to search for suitable free space and occasionally perform more expensive internal bookkeeping behind the scenes. For a latency sensitive inference service that needs to respond within a strict, predictable time budget for every single request, that variability alone can be a serious problem, regardless of how fast it usually is on average. Pre allocating all the memory a prediction will ever need well ahead of time keeps the actual prediction step doing pure, predictable computation with none of that timing uncertainty.

C++

When a loop accesses array elements in the same straightforward order they are actually laid out in memory, the processor can load an entire nearby chunk into its fast cache at once and then serve many subsequent accesses directly from that cache, which is dramatically faster than reading from main memory every single time. Accessing the same array in a scattered, unpredictable, or reversed pattern instead defeats this benefit almost entirely, forcing frequent, slow round trips back to main memory and often slowing an otherwise identical loop down significantly, even though the total amount of arithmetic performed has not changed at all.

C++

Useful signals include request latency and its distribution rather than just the average, error rates broken down by type such as timeouts versus rate limits, token usage and cost per request, and cache hit rates if caching is in place. Alerting on a sustained spike in errors or latency lets a team catch a struggling AI provider or a runaway cost issue before it becomes a bigger problem.

JavaScript

Many preprocessing steps, such as transforming each row or file independently, are naturally parallel since one item does not depend on another. Splitting the work across multiple processes lets each one handle a portion of the data at the same time on a different CPU core, sidestepping the GIL entirely since each process has its own interpreter. The main overhead to watch for is the cost of splitting and recombining the data, which can eat into the gains if each individual chunk of work is very small.

Python

The endpoint should validate and sanitize the incoming input, apply a reasonable length limit, attach the API key on the server rather than trusting anything from the client, enforce authentication and rate limiting on who can call it, and catch errors from the provider to return a clean, generic error message rather than leaking internal details or the raw provider response back to the caller.

JavaScript

Processing several inputs together as one larger batch lets the underlying numerical operations, and any specialized hardware such as a GPU, work on significantly more data per operation, which is generally far more efficient than repeating the same fixed overhead separately for every single individual request. This substantially improves overall system throughput, the total number of predictions completed per second, though it does typically introduce a small amount of additional latency for any individual request, since it may need to wait briefly for enough other requests to arrive before its batch is considered complete and gets processed.

C++

If the same expensive feature calculation is needed repeatedly, either across multiple runs or multiple parts of a pipeline, storing the computed result to disk or in memory the first time and reusing it afterward avoids repeating the work unnecessarily. This is especially valuable for features that depend only on raw input data that rarely changes, though care is needed to invalidate the cache whenever the underlying data or the feature logic itself is updated.

Python

Sensitive fields should ideally be detected and stripped or masked before ever being sent to a third-party AI provider, and the application should clearly disclose what data is sent and how it might be used or retained. Logging on the server should avoid capturing full prompts containing sensitive data, and access to any stored logs or transcripts should be restricted, since a data leak through an AI feature is still a data leak.

JavaScript

A fixed size buffer, whose capacity is already known and reserved well ahead of time, never needs to pause and reallocate a larger block of memory partway through processing, unlike a growable container that may occasionally need to resize itself once it unexpectedly exceeds its current internal capacity. That occasional resizing operation, however rare it might be on average, introduces an unpredictable timing spike exactly when it happens, which is precisely the kind of inconsistency a genuinely latency sensitive system, one that must respond within a strict, guaranteed time limit, generally cannot afford to tolerate even occasionally.

C++

Common strategies include removing rows or columns with too many missing values when little useful information would be lost, filling gaps with a simple statistic such as the mean or the most common value, or using a more sophisticated method that predicts a likely value based on other related columns. The right choice depends heavily on how much data is missing, whether it is missing at random, and what the specific model being trained can tolerate.

Python

The AI API call is typically mocked in automated tests, returning fixed, predictable sample responses so the surrounding logic, such as parsing, error handling and rendering, can be tested reliably without depending on the real network call or its inherent variability. Separately, a smaller set of manual or scheduled integration tests against the real API helps catch cases where the provider's actual behavior has drifted from what the mocks assume.

JavaScript

For any numerical operation that is common and well understood, such as multiplying two matrices together, an existing, mature, heavily optimized library has almost certainly already been carefully tuned for the target hardware in ways that would be difficult to fully replicate independently, so reaching for that library first is usually the sensible default. Writing a custom routine tends to make sense only when the operation is unusual enough that no suitable library covers it well, or when a specific, well measured bottleneck has already been identified through careful profiling and clearly justifies that extra engineering effort.

C++

Reproducibility generally requires fixing random seeds used by any process that involves randomness, such as data shuffling or model initialization, recording the exact versions of every library used, and capturing precisely which version of the dataset and which configuration values produced a given result. Without all of these pinned down together, rerunning what looks like the same experiment can quietly produce a different outcome, making it hard to trust or compare results over time.

Python