ML.NET and ONNX questions still show up in senior .NET interviews, even in an era dominated by large language models, because plenty of production systems need fast, cheap, explainable predictions on structured data — fraud scoring, churn prediction, demand forecasting — where a classic model is the right tool, not an LLM. Interviewers use this material to check whether a candidate understands machine learning as an engineering discipline with its own pipeline, evaluation and deployment concerns, not just as a library call, and whether they know when reaching for a large language model would actually be the wrong choice. This page works through the senior-level questions on ML.NET's pipeline model, feature engineering, evaluation metrics, production deployment with PredictionEnginePool, ONNX interoperability, and the operational discipline of monitoring and retraining a model once it's live.

Q1 Walk through ML.NET's pipeline abstraction: IDataView, estimators and transformers. Why is it designed this way?#

Short answer: IDataView is a lazy, schema-based, columnar view over data that only materializes rows as they're enumerated; an IEstimator<TTransformer> represents an untrained pipeline step with a Fit(IDataView) method that produces a trained ITransformer, and an ITransformer has a Transform(IDataView) method that applies the learned operation — composing estimators with .Append() builds a training pipeline that, once fit, becomes a single composed transformer you can run repeatedly.

This separation exists mainly for streaming and composability. IDataView streams row by row instead of loading a full dataset into memory, which lets ML.NET pipelines process files far larger than available RAM without special-casing "big data" — the schema is known up front, but values are only pulled through as each stage consumes them. Estimator and transformer separation exists because training and scoring are genuinely different operations: an estimator's Fit needs training data to learn something, a normalization's min and max, a one-hot encoder's category list, while the resulting transformer's Transform just applies what was learned to new data without needing labels or retraining, which is exactly the operation you run at inference time in production. Chaining several Append() calls composes many small, independently testable estimators, one for feature engineering, one for normalization, one for the trained algorithm, into a single pipeline that, once fit, is one composed ITransformer you can save, load and run as a unit; that unit is what actually gets deployed.

C#
var mlContext = new MLContext(seed: 1);
var pipeline = mlContext.Transforms.Categorical.OneHotEncoding("CategoryEncoded", "Category")
    .Append(mlContext.Transforms.Concatenate("Features", "CategoryEncoded", "Amount"))
    .Append(mlContext.Transforms.NormalizeMinMax("Features"))
    .Append(mlContext.BinaryClassification.Trainers.LbfgsLogisticRegression());

ITransformer model = pipeline.Fit(trainingData);
IDataView predictions = model.Transform(testData);

What interviewers look for: the estimator-versus-transformer distinction stated precisely — learns versus applies — and why lazy IDataView matters for datasets that don't fit in memory.

Common mistakes: treating IDataView as just "ML.NET's DataFrame" without recognizing it's lazily evaluated, and not knowing that a fitted pipeline is itself a single ITransformer you deploy as one unit.

Q2 How do you approach feature engineering in ML.NET for a tabular dataset?#

Short answer: Encode categorical columns, typically one-hot or hashing for high-cardinality categories, featurize text columns into numeric vectors, normalize numeric columns to comparable scales, handle missing values explicitly rather than letting them silently propagate, and concatenate all resulting feature columns into a single Features column, the shape most ML.NET trainers expect as input.

Most raw tabular columns aren't directly usable by a trainer: a categorical string column needs OneHotEncoding, or OneHotHashingEncoding when cardinality is too high for a dense vector to be practical; a free-text column needs FeaturizeText, which handles tokenization, stop-word removal and n-gram vectorization in one call; and numeric columns on very different scales, a dollar amount next to a count, generally benefit from NormalizeMinMax or NormalizeMeanVariance so no feature dominates purely because of its scale rather than its actual signal. Missing values need an explicit decision: ReplaceMissingValues lets you choose a strategy, a default value, a mean, or a flag column indicating a value was missing, and silently letting nulls flow into a trainer without a conscious choice produces models whose behavior is hard to explain later. Concatenate ties it together, combining every engineered feature column into one Features vector column, since ML.NET trainers are written against that single-column convention rather than reading many separate named columns. Feature engineering is also where domain knowledge earns its keep — a raw timestamp is rarely useful on its own, but day-of-week, hour-of-day or time-since-last-event derived from it frequently carries most of the real signal, and no generic transform performs that derivation for you.

What interviewers look for: naming the concrete transform for each data type — categorical, text, numeric, missing — and explaining why concatenation into a single Features column is required, not just that it's a step.

Common mistakes: feeding raw categorical strings or unnormalized wide-range numeric columns directly into a trainer, and letting missing values pass through without an explicit strategy.

Q3 How do you choose and interpret evaluation metrics for classification, regression and ranking tasks in ML.NET?#

Short answer: Match the metric to the task's cost structure, not just its category: binary classification leans on AUC and F1 score over raw accuracy when classes are imbalanced, regression leans on RMSE or MAE depending on how you want to weight large errors, and ranking uses NDCG because it rewards getting the top results right more than getting the full order right — and every one of these should be computed with cross-validation, not a single train/test split, before you trust it.

Accuracy is the most misleading metric to lead with on an imbalanced binary classification problem — a dataset that's 95% negative gets 95% accuracy from a model that predicts negative every time, which is why BinaryClassificationMetrics also exposes AreaUnderRocCurve, a threshold-independent measure of separability, F1Score, the harmonic mean of precision and recall, and LogLoss, which penalizes confident wrong predictions more than accuracy ever does. Regression metrics split on how they weight errors: RootMeanSquaredError squares errors before averaging, penalizing large misses disproportionately, appropriate when one big miss is much worse than several small ones, while MeanAbsoluteError treats every unit of error linearly, the more honest metric when errors of any size matter proportionally; RSquared shows how much variance the model explains relative to a naive mean-predicting baseline, a useful sanity check but not a substitute for either error metric. Ranking metrics like NDCG matter because a ranking task cares disproportionately about the top few results — a search or recommendation system that gets position one wrong but position fifteen right has a much worse NDCG than one that made the opposite mistake, matching how users actually experience ranked results. None of these numbers mean much from a single train/test split, since that split can be lucky or unlucky; mlContext.BinaryClassification.CrossValidate, and its regression and multiclass equivalents, trains and evaluates across several folds and reports the metric's mean and variance, which is what you actually cite as the model's performance.

What interviewers look for: picking a metric based on the task's actual cost structure — imbalance, error-size sensitivity, top-of-list importance — rather than reflexively citing accuracy, and mentioning cross-validation as the default evaluation method.

Common mistakes: reporting accuracy as the headline metric on an imbalanced classification problem, and evaluating on a single train/test split without cross-validation or a held-out final test set.

Q4 How do you deploy an ML.NET model for real-time inference in an ASP.NET Core API, and why use PredictionEnginePool instead of PredictionEngine directly?#

Short answer: Register a PredictionEnginePool<TInput, TOutput> with AddPredictionEnginePool<TInput, TOutput>().FromFile(modelName: "...", filePath: "...", watchForChanges: true) in dependency injection and inject the pool into endpoints instead of constructing a PredictionEngine directly, because PredictionEngine is not thread-safe — the pool wraps a set of PredictionEngine instances in an object pool so concurrent requests each get an instance to themselves instead of racing on one shared object or paying the cost of creating a new one per request.

A naive implementation that shares a single PredictionEngine across requests corrupts predictions under concurrent load because it isn't thread-safe, while creating a brand-new instance per request avoids the race but re-pays model loading and initialization overhead on every call, wasteful at any real request volume. PredictionEnginePool, provided via Microsoft.Extensions.ML, solves both problems by pooling pre-warmed instances behind the DI-registered pool object: a request rents an instance, uses it, and returns it, giving thread-safe concurrent access with initialization cost paid once per pooled instance rather than once per request. Registering with FromFile(..., watchForChanges: true) adds a FileSystemWatcher on the model file, so replacing the file on disk with a newly trained version triggers an automatic reload without an application restart, the mechanism that makes zero-downtime model updates practical; FromUri(..., period: ...) does the equivalent for a remotely hosted model, polling on the given interval instead of watching a local file. The modelName parameter lets one application register and serve multiple distinct models from the same pool infrastructure, each reloaded independently.

C#
builder.Services.AddPredictionEnginePool<ModelInput, ModelOutput>()
    .FromFile(modelName: "fraud-model", filePath: "Models/fraud.zip", watchForChanges: true);

app.MapPost("/predict", (ModelInput input, PredictionEnginePool<ModelInput, ModelOutput> pool) =>
    Results.Ok(pool.Predict(modelName: "fraud-model", example: input)));

What interviewers look for: the specific thread-safety reason for pooling, not just "it's faster," and knowing watchForChanges as the mechanism for hot model reloads without a restart.

Common mistakes: sharing a single PredictionEngine across concurrent requests, and creating a new PredictionEngine per request instead of using the pool.

Q5 What is ONNX, and when do you export or import a model to or from ONNX in a .NET inference pipeline?#

Short answer: ONNX is an open, framework-neutral format for representing trained models, and in .NET you use it in two directions: exporting an ML.NET model to ONNX with the Microsoft.ML.OnnxConverter package when another runtime or platform needs to run it, and importing a model trained elsewhere, such as PyTorch or TensorFlow, by running it inside an ML.NET pipeline via mlContext.Transforms.ApplyOnnxModel(...) from the Microsoft.ML.OnnxTransformer package, powered by the Microsoft.ML.OnnxRuntime inference engine.

The interoperability problem ONNX solves is real: a data science team might train a vision or NLP model in PyTorch, but the production .NET service needs to run inference without taking a dependency on a Python runtime — exporting that model to ONNX and loading it with Microsoft.ML.OnnxRuntime, or through ML.NET's ApplyOnnxModel transform, which wraps the same runtime inside a normal ML.NET pipeline alongside ordinary feature-engineering steps, closes that gap cleanly, running native inference in-process from C#. The other direction, exporting an ML.NET-trained model to ONNX, matters when the model needs to run somewhere ML.NET itself doesn't reach — a mobile app, a browser through ONNX Runtime Web, or a non-.NET service — and it's a separate, purpose-built package rather than a built-in method on every trainer, because not every ML.NET trainer's internal representation maps cleanly onto ONNX's operator set. When you use ApplyOnnxModel inside an ML.NET pipeline, you're still responsible for the ML.NET-side feature engineering, resizing an image, extracting pixels, tokenizing text, before the tensor reaches the ONNX model, since the model itself only knows how to consume the exact input tensor shape it was trained and exported with, specified through the transform's input and output column names.

C#
var pipeline = mlContext.Transforms.LoadImages("image", imageFolder, nameof(ImageInput.ImagePath))
    .Append(mlContext.Transforms.ResizeImages("image", imageWidth, imageHeight))
    .Append(mlContext.Transforms.ExtractPixels("image"))
    .Append(mlContext.Transforms.ApplyOnnxModel(
        inputColumnNames: new[] { "image" },
        outputColumnNames: new[] { "output" },
        modelPath: "Models/model.onnx"));

What interviewers look for: the two directions, export for portability and import for reuse, stated distinctly, plus naming the actual packages involved rather than treating "ONNX support" as one undifferentiated feature.

Follow-up questions:

  • What would you check before trusting that an exported ONNX model produces identical predictions to the original ML.NET model?
  • Why might an ONNX-imported model require a different pre-processing pipeline than one trained natively in ML.NET?

Q6 How do you diagnose and handle model drift after a model is deployed?#

Short answer: Distinguish data drift, the distribution of incoming feature values shifting away from what the model was trained on, from concept drift, the actual relationship between features and the target changing, since they have different symptoms and fixes; monitor both by comparing live feature distributions against the training baseline and by tracking a live proxy for model quality, such as delayed ground-truth labels, rather than assuming a deployed model keeps performing at its validation-time accuracy indefinitely.

Data drift shows up first and is easier to detect, because it only requires comparing statistics of incoming feature values, mean, variance, category frequencies, against the training-time distribution, without needing new ground-truth labels at all; a fraud model trained before a new payment method launched will see a feature distribution it never encountered once that payment method is in real use, regardless of whether its decision boundary is still correct. Concept drift is harder to catch because it specifically requires knowing whether predictions are still correct, which needs ground truth that often arrives late, a fraud label confirmed weeks later, a churn outcome known only after the fact, so concept-drift monitoring typically lags real-world change by however long labels take to arrive, and that lag is a design constraint worth calling out explicitly. In practice, teams monitor both: automated feature-distribution checks that run immediately and flag drift within hours, plus a slower, label-dependent quality metric confirming whether predictive accuracy holds up once enough ground truth accumulates. A retraining trigger should be tied to a threshold on one or both signals rather than a purely calendar-based schedule, since a stable, low-drift model can go a long time without needing retraining while a rapidly drifting one can degrade badly well before a fixed monthly retrain would catch it.

What interviewers look for: the data-drift-versus-concept-drift distinction stated precisely, including that concept drift needs ground truth and therefore lags, which shows real operational experience.

Common mistakes: monitoring only feature drift and assuming that implies predictions are still accurate, and retraining purely on a fixed calendar schedule instead of a signal-driven trigger.

Q7 How do you design an automated retraining pipeline for a production ML.NET model?#

Short answer: Trigger retraining on a schedule or a drift or quality signal, train the new candidate against a held-out validation set that mirrors current production data, gate promotion behind a comparison against the currently deployed model's metrics rather than promoting automatically, and roll the new model out through the same hot-swap mechanism, FromFile with watchForChanges, or FromUri, that supports safe rollback if the new model underperforms once it sees live traffic.

The retraining job itself mostly repeats the original training pipeline against a refreshed dataset that includes recent data, but the part that actually prevents incidents is the promotion gate: a newly trained candidate should be evaluated with the same metrics and cross-validation discipline as the original model and compared directly against the currently deployed model's metrics on a shared, held-out validation set, and only promoted if it's at least as good, not automatically deployed just because a scheduled job produced a new artifact. Versioning the model file, alongside the training data snapshot and the code and configuration that produced it, is what makes rollback possible and makes "which model is live" answerable during an incident, rather than something reconstructed from logs afterward. The deployment mechanism that makes this practical without downtime is the same one used for any model update: writing the new, validated model file to the path PredictionEnginePool is watching, or the URI it's polling, triggers the pool's automatic reload, so promotion becomes replacing the artifact behind an already-tested hot-reload path rather than a code deployment, and rollback is symmetric, replacing it with the previous version's artifact. A retraining pipeline without an automated evaluation gate is not actually safer than manual retraining; it's manual retraining with the human review step removed, a regression in safety dressed up as automation.

What interviewers look for: the promotion gate as the load-bearing part of the design, comparing the new candidate against the current production model before swapping, rather than "retrain on a schedule and deploy."

Common mistakes: automatically promoting every retrained model without comparing it against the currently deployed one, and not versioning the training data and configuration alongside the model artifact, which makes an incident nearly impossible to root-cause later.

Q8 When do you reach for classic ML with ML.NET instead of an LLM, and when is that the wrong choice?#

Short answer: Classic ML is the right choice for structured or tabular data with a well-defined target, where latency, cost and explainability matter and the feature set is stable, fraud scoring, churn prediction, demand forecasting, while an LLM is the right choice for unstructured language or multimodal tasks and open-ended instructions where the "features" are essentially free text that would be expensive and brittle to hand-engineer; the wrong choice in either direction usually shows up as a slow, expensive, hard-to-explain model doing a job a small classifier could do in microseconds, or a rigid classic pipeline straining to handle inputs it was never designed to parse.

The strongest case for classic ML is a large amount of structured historical data, a clearly labeled target, and a latency budget measured in microseconds to low milliseconds — a real-time fraud check on a payment has to return in that window, run at very high request volume, and be explainable enough to satisfy a compliance reviewer asking why a transaction was flagged, and a small, well-tuned classic model, often served through PredictionEnginePool or exported to ONNX for cross-platform inference, is dramatically cheaper and faster than routing every transaction through an LLM call, with a decision that's auditable through feature importance rather than a generated explanation that may or may not reflect the model's real reasoning. LLMs earn their cost and latency where the input is genuinely unstructured or the task requires flexible language understanding that would be brittle to hand-engineer as features: classifying free-text support tickets by intent, extracting structured data from varied documents, or handling a request whose shape isn't known in advance. A mature system frequently uses both together rather than picking one globally, letting an LLM extract or normalize features from unstructured input that then feeds a fast, explainable classic model for the actual decision, combining the LLM's flexibility on the messy input side with the classic model's speed, cost and auditability on the decision side. The interview-relevant failure mode to name is reaching for an LLM by default, for a task a logistic regression or gradient-boosted tree would solve faster, cheaper and more explainably.

What interviewers look for: a concrete decision framework — data structure, latency, cost, explainability — rather than a general preference for one technology, and the hybrid pattern of an LLM feeding a classic model as a sign of practical thinking.

Common mistakes: defaulting to an LLM for a task with abundant labeled structured data and a hard latency budget, and dismissing classic ML as outdated instead of recognizing where it's still the better engineering choice.

Q9 How do you handle class imbalance and overfitting in an ML.NET pipeline?#

Short answer: For imbalance, address it with resampling, undersampling the majority class or oversampling the minority class, or class weighting where the trainer supports it, and always evaluate with metrics that don't hide the minority class, AUC and F1 rather than accuracy; for overfitting, rely on cross-validation to detect the gap between training and validation performance, keep a genuinely untouched final test set, and use the trainer's regularization hyperparameters rather than just adding more data or features.

A model trained naively on an imbalanced dataset learns to predict the majority class most of the time because that minimizes average loss even while being nearly useless for the minority class that usually matters more; fraud, churn and defect detection are rarely balanced problems. Resampling changes the training distribution the model actually learns from, while class weighting keeps the original distribution but tells the trainer to penalize minority-class mistakes more heavily during optimization, and the right choice depends on the trainer and how much synthetic distortion of the training set is acceptable. Overfitting is a distinct problem from imbalance and shows up as a large gap between training-set and validation-set metrics: a model with excellent training accuracy and much worse cross-validated performance has memorized noise in the training set rather than learned a generalizable pattern, and the fix is rarely "more data" alone — it's more often reducing model complexity, applying the trainer's regularization parameters, L1 or L2 penalty strength, tree depth and leaf-count limits for tree-based trainers, or simplifying an overly large feature set giving the model room to fit noise. The discipline that catches both problems before deployment is holding out a final test set that never participates in cross-validation or hyperparameter tuning, reserved purely to confirm the model performs as expected on genuinely unseen data right before it ships, since a model can be quietly tuned to overfit even the cross-validation folds if reused too many times across iteration.

What interviewers look for: treating imbalance and overfitting as distinct problems with distinct diagnostics, metric choice for imbalance, train and validation gap for overfitting, and naming concrete regularization or resampling techniques rather than a vague "get more data."

Common mistakes: using accuracy to judge a model on an imbalanced dataset, and tuning hyperparameters against the same validation set repeatedly without a final, untouched test set to confirm the result generalizes.

Q10 How do you version and test ML.NET models as software artifacts, not just as experiments?#

Short answer: Treat the trained model file as a versioned build artifact the same way you'd version a compiled binary, tied to the exact training data snapshot and code or configuration that produced it, and write integration tests that load the saved model and assert its predictions on a small set of fixed, known inputs stay within an acceptable tolerance, so a change to the training pipeline that silently changes model behavior fails a test instead of shipping unnoticed.

A model file produced by a training run that isn't tied to a specific, reproducible combination of code version, hyperparameters and data snapshot is nearly impossible to debug later — "why did the model start behaving differently" is unanswerable if you can't reconstruct exactly what produced the model currently deployed, so the training pipeline's output should be versioned and stored alongside metadata identifying the data snapshot and configuration that generated it, the same discipline as any other build artifact. Testing a model differs from testing ordinary code because you're not asserting exact output for arbitrary input, you're asserting that specific, curated inputs produce predictions within an acceptable tolerance of a known-good value, and that the input and output schema the pipeline expects hasn't silently changed — a schema mismatch, a renamed or reordered feature column, is a common, easy-to-miss regression a fixed-input integration test catches immediately, while a silent accuracy regression on the full evaluation set is what the retraining pipeline's promotion gate is for. These tests belong in the same CI pipeline as any other integration test, run against the actual saved model artifact rather than retraining inside the test, both for speed and because the point is verifying the artifact that will actually ship, not a freshly retrained stand-in for it.

C#
[Fact]
public void FraudModel_FlagsKnownFraudulentPattern()
{
    var mlContext = new MLContext();
    ITransformer model = mlContext.Model.Load("Models/fraud.zip", out var schema);
    var engine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(model);

    var result = engine.Predict(KnownFraudulentExample);

    Assert.True(result.Probability > 0.8f);
}

What interviewers look for: treating the model artifact as a versioned build output with reproducible provenance, and describing fixed-input integration tests as a schema-and-sanity check distinct from full evaluation-metric regression testing.

Common mistakes: treating a trained model as a throwaway experiment output instead of a versioned artifact tied to its training data and code, and having no automated test that would catch a silently broken input schema before it reaches production.

Quick-Fire Round#

QuestionAnswer
What does an IEstimator<TTransformer>'s Fit method produce?A trained ITransformer.
Why is PredictionEnginePool used instead of a shared PredictionEngine?PredictionEngine isn't thread-safe; the pool provides safe, reusable instances.
What option enables automatic model reload from a local file?watchForChanges: true on FromFile, backed by a FileSystemWatcher.
What package exports an ML.NET model to ONNX format?Microsoft.ML.OnnxConverter.
What method runs an ONNX model inside an ML.NET pipeline?mlContext.Transforms.ApplyOnnxModel(...).
What's the difference between data drift and concept drift?Data drift is input distribution shift; concept drift is the feature-target relationship changing.
What metric is misleading on an imbalanced binary classification task?Accuracy.
What should gate promoting a retrained model to production?A metric comparison against the currently deployed model, not just a successful training run.

How to Prepare#

  • Build one ML.NET pipeline end to end — feature engineering, training, cross-validated evaluation, and serving through PredictionEnginePool — so the estimator and transformer distinction is concrete.
  • Practice explaining the thread-safety reason for PredictionEnginePool without hesitating; it comes up almost every time deployment is discussed.
  • Know both ONNX directions, ApplyOnnxModel to consume and Microsoft.ML.OnnxConverter to export, and be ready to name the packages involved.
  • Rehearse the data-drift-versus-concept-drift distinction and why concept drift monitoring lags ground-truth availability.
  • Have a clear decision framework ready for classic ML versus LLM, including the hybrid pattern of using one to feed the other.
  • Practice describing a promotion gate for automated retraining; "retrain and deploy automatically" without a comparison step is the answer that raises concerns.