Version Control and Developer Tooling Questions
The everyday toolchain of software work: version control with Git (branching, merging, rebasing, conflict resolution, using git bisect to find a regression), command-line and shell proficiency for day-to-day navigation, log inspection, and troubleshooting, IDE and editor workflows, build systems and package/dependency management (npm, Maven, pip, Gradle, CocoaPods, and embedded/cross-compilation toolchains), and the growing practice of AI-assisted coding: using, reviewing, and verifying AI-generated code and tests. Deliberately generic across languages and stacks; language- and domain-specific frameworks live in their own categories. This topic covers a developer's individual command of these tools, not: writing durable shell automation and glue scripts (Shell Scripting and Automation owns that), producing, versioning, and publishing build artifacts or container images (Build Automation and Artifact Management owns that), release cadence and change governance (Release Management and Change Control owns that), or diagnosing a live production incident end to end (Performance Troubleshooting and Incident Response and the Observability topics own that).
An AI assistant produces a training script for an imbalanced classification problem using accuracy as the main metric and a random train-test split. Review the approach: what problems do you see, and how would you correct them?
Sample Answer
Direct answer
Three problems stand out: accuracy is the wrong headline metric for imbalanced data because it rewards a model for ignoring the minority class, a plain random split can break temporal or grouped structure in the data and leak information between train and test, and there is no threshold-aware evaluation at all. I would fix this with a stratified split (or a time-based split if the data has a time axis), report precision, recall, F1, and PR AUC (precision-recall area under the curve, a metric that summarizes performance across thresholds and is more informative than accuracy when positives are rare) instead of accuracy, and keep any resampling or class weighting strictly inside the training fold, using a pipeline so the split stays honest end to end.
Structured elaboration
Why accuracy is misleading here. Consider a dataset of 1,000 examples where 950 are negative and only 50 are positive (a 5% positive rate). A model that predicts "negative" for every single example never learns anything about the positive class, yet: true positives = 0, false positives = 0, false negatives = 50, true negatives = 950. Accuracy = 950/1000 = 0.95, a 95% score, while recall = 0/50 = 0%, meaning it catches literally none of the cases you actually care about. Precision is undefined (0 predicted positives). A 95%-accurate, completely useless model is the exact failure mode accuracy hides.
Why a random split is risky. If the data has any temporal or grouped structure (transactions over time, multiple rows per customer), a random split can put future information into the training set or split a customer's rows across both train and test, both of which inflate the reported score relative to how the model will actually perform in production. The fix is a stratified split when class balance is the only concern, and a time-based split (train on earlier data, test on later data) whenever the data has a genuine time axis, since stratification alone does not address temporal leakage.
Correcting the training script. Report precision, recall, F1, and PR AUC instead of, not in addition to, treating accuracy as the headline number. Keep any preprocessing or class-imbalance handling (resampling, class weights) inside a pipeline that is fit only on the training fold, so cross-validation numbers stay honest rather than leaking information from validation data into preprocessing decisions.
Worked example
A second worked example on the loss side. Suppose the same AI assistant later suggests softmax activation with categorical cross-entropy for a task where each example can carry more than one label at once, for instance tagging a support ticket with several relevant categories simultaneously (billing and account-access at the same time). Softmax forces the predicted probabilities across classes to sum to 1, which assumes exactly one correct class per example, so it is the wrong choice for a genuinely multi-label problem. The correct setup is a sigmoid activation on each output unit (an independent probability per class) with binary cross-entropy (BCE, a loss function that scores each class's yes/no prediction independently) summed or averaged across labels, and metrics reported per label rather than as a single top-1 accuracy.
Checking AI-generated feature SQL for the same imbalance-distorting leakage. If the features for this dataset come from AI-generated SQL, two specific bugs are worth checking by hand: a window function whose boundary silently includes future rows (ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING instead of ... AND CURRENT ROW, which lets a rolling average see data from after the prediction timestamp), and a join that fans out one training row into several duplicates because the joined table is not unique per key, which distorts the apparent class balance, especially for a rare positive class.
Verifying a claimed win rather than accepting it. If a teammate or an AI assistant reports "switched the resampling technique, F1 improved," that single number is not enough to accept the change. I would check precision and recall separately (F1 can rise even while recall quietly collapses, if precision spikes enough to compensate) and check calibration (whether the model's predicted probabilities still mean what they claim, for example by comparing predicted-probability buckets against the observed positive rate within each bucket) before accepting that the change is actually an improvement rather than a different, worse trade-off wearing a better-looking single number.
Trade-offs and pitfalls
Stratification fixes class ratio but does nothing for temporal leakage, so time-ordered data needs both considerations together, not one instead of the other. Aggressive resampling can improve recall while quietly hurting calibration, so a "better" F1 is not automatically a better model for a use case where the actual predicted probability matters, not just the class label.
If an AI assistant proposes a novel model architecture or training trick that you do not fully understand, how do you decide whether to prototype it, ask for more evidence, or reject it outright?
Sample Answer
Direct answer
I use a simple decision framework built around four questions: how novel is the idea really, what is the realistic expected gain, what is the risk if it goes wrong, and how much evidence actually supports it right now. Not fully understanding an idea is not, by itself, a reason to reject it, engineers accept genuinely useful ideas they do not deeply understand all the time, but it is a reason to demand more evidence before committing real time or production risk to it.
Structured elaboration
Prototype it when
- The idea is compatible with existing infrastructure and the current data pipeline, so a test does not require a parallel system just to try it.
- The potential upside is large enough to justify a short, bounded experiment.
- A clean baseline and a clear success metric already exist, so the experiment can produce an unambiguous answer.
Ask for more evidence when
- The method is genuinely novel, but the explanation for why it should help is thin or hand-wavy.
- The assistant cannot articulate why this should outperform the current baseline, only that it might.
- The trick looks fragile, expensive to run, or hard to debug if it misbehaves in production.
Reject outright when
- It depends on unsupported infrastructure, unmaintainable custom operations, or math that nobody on the team, including the assistant, can actually explain clearly.
- It adds real training or serving complexity without a measurable, demonstrated gain to justify that cost.
- It conflicts with a compliance, latency, or reliability constraint the system already has to meet.
Worked example
Suppose the AI assistant proposes a new attention variant, claiming it should improve model quality, along with a rough implementation. Rather than accepting or rejecting on the description alone: I would ask for the specific mechanism and why it should help this particular task, not just "attention variants generally help." I would ask what its memory and compute cost looks like relative to the current approach, since a change that meaningfully increases training cost needs a correspondingly meaningful expected gain to be worth prototyping at all. Then I would run a small, controlled ablation: same data, same baseline configuration, only the attention mechanism changed, with a predetermined evaluation metric decided before running the experiment, not chosen afterward to make the result look better. If the assistant cannot explain the mechanism's failure modes (when would this plausibly hurt rather than help), that gap itself is useful information, since it tells me the risk of adopting it blind, even if the experiment result looks good, is higher than for a well-understood technique.
Trade-offs and pitfalls
A staff-level habit worth protecting: research curiosity is valuable, and "I don't fully understand this yet" should not automatically mean "no." But production systems do not reward cleverness for its own sake, they reward reliability and explainability when something eventually goes wrong. The failure mode to actively guard against is adopting something because a benchmark number looked good without understanding why, since that leaves you unable to debug it later, unable to predict when it will fail on a different distribution of data, and unable to explain it to anyone who asks. Bounding the cost of the experiment (a small, cheap ablation before any real commitment) is what makes it safe to stay curious without exposing production to something nobody on the team can reason about.
Before pasting AI-generated code into a production repository, what checklist do you use to validate correctness, security, style, licensing, and test coverage?
Sample Answer
Direct answer
I run through a fixed checklist covering correctness, security, style, licensing, documentation accuracy, and test coverage, in that rough priority order, before any AI-generated code enters a production repository. Correctness and security come first because they are the categories where a subtle mistake causes real damage; style and documentation matter, but a stylistically perfect, well-documented function that is subtly wrong is still a bad outcome.
Structured elaboration
Correctness
- Run the code on a small, known input and compare the actual output to the expected output by hand.
- Review edge cases explicitly: empty input, boundary values, unexpected types.
Security
- Look for unsafe deserialization (loading data in a way that can execute arbitrary code), shell or command injection (user-controlled input reaching a shell command unsanitized), secret leakage (a hardcoded key or token), and unvalidated file or network access.
Style
- Confirm the code follows the project's existing conventions: naming, logging patterns, and whatever the project's linter already enforces.
Licensing
- Verify that any snippet the assistant appears to have reproduced closely (a distinctive, non-trivial block that reads like it came from somewhere specific) is not lifted from a source with a restrictive license, since copied code can carry legal obligations the rest of the codebase does not have.
Tests
- Add unit tests covering the normal path, at least one failure path, and any regression case relevant to why the change was made in the first place.
Checking whether AI-written documentation actually matches the code
A specific checklist item worth calling out on its own: when an AI tool also generates docstrings or inline comments alongside the code, verify the documentation actually describes what the code does, not what the code was probably intended to do. A concrete way to check this: read the docstring first, form an expectation of the function's behavior purely from that description, then read the code and see if it actually matches. A mismatch (the docstring claims a default that the code does not actually apply, or describes a parameter the function no longer accepts after an edit) is a specific, easy-to-miss failure mode, since documentation reads as authoritative and reviewers tend to trust it rather than re-deriving behavior from the code every time.
Worked example
A pull request adds a function with an AI-generated docstring: "Returns the top N results, sorted by score descending. If N exceeds the number of available results, returns all of them." Reading only the code, the function actually raises an IndexError if N exceeds the available count, rather than gracefully returning everything, a real behavior gap between the stated contract and the implementation. Catching this before merge (by deliberately reading the docstring and the code as two separate sources of truth and comparing them) avoids the situation where downstream callers write code trusting a documented contract the function does not actually honor.
Trade-offs and pitfalls
For ML (machine learning) code specifically, add checks for data leakage, non-determinism, and version-specific API usage, since those are failure modes generic code review checklists do not usually cover. I do not trust generated code until it passes CI locally, has a readable diff, and I understand every dependency it introduces; for anything touching model training or inference, I also sanity-check that the change did not silently change resource usage (memory, latency) in a way that would only show up once it hits production traffic.
An AI assistant generates a PyTorch training loop for you. Before you trust it and run it on real data, what would you check line by line, and what are the classic mistakes AI-generated training loops tend to make that are easy to miss on a quick read?
Sample Answer
Direct answer
I would read the loop line by line checking five specific things: device placement, gradient zeroing, the forward pass, the loss computation, and the backward/step sequence, since these are exactly where a quick read tends to skim past a subtly wrong line that still runs without error. PyTorch is not installed in the environment I am writing this answer in, so the training-loop excerpt below is illustrative for review purposes, not a claimed execution trace; I am not presenting fabricated output for it.
What I check, line by line
for epoch in range(num_epochs):
model.train()
for batch in dataloader:
inputs, labels = batch
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
- Device placement: are the model and every batch tensor moved to the same device before the forward pass? A model on GPU receiving CPU tensors (or vice versa) throws an error, but a subtler version, part of the pipeline (like a mask or an auxiliary tensor built later in the loop) never getting
.to(device), can slip past a quick read. - Gradient zeroing: does
optimizer.zero_grad()run at the start of every iteration, beforeloss.backward()? If it is missing or misplaced, gradients accumulate silently across batches instead of being computed fresh each time, and training still runs, it just learns something subtly wrong. - Forward pass: are inputs, labels, and any masks shaped the way the model expects, and is there an accidental
.cpu()call anywhere that would silently move a tensor off the GPU mid-pipeline? - Loss computation: is the loss a scalar, does it use the label format the loss function actually expects, and is any reduction (mean vs sum across the batch) intentional rather than a framework default nobody checked?
- Backward and step order: does
loss.backward()happen beforeoptimizer.step(), and is nothing calling.detach()on a tensor that still needs its gradient?
I also check for model.train() at the start of training and model.eval() plus torch.no_grad() (a context manager that disables gradient tracking, since validation does not need it and skipping it wastes memory and time) during validation, since AI-generated loops sometimes omit the mode switch entirely, which silently changes the behavior of layers like dropout and batch normalization between training and evaluation.
Classic mistakes that are easy to miss on a quick read
- Missing or misplaced
optimizer.zero_grad(), so gradients silently accumulate across batches instead of resetting each step. - No
model.eval()/model.train()switching, so dropout and batch normalization behave inconsistently between training and validation without any error being raised. - Validation code missing
torch.no_grad(), which still produces correct numbers but wastes memory and time computing gradients nobody uses. - A loss function and label shape that do not actually match (for example a multi-class setup mixing one-hot labels with a loss function that expects integer class indices), which either errors or, worse, silently computes something other than the intended loss.
Seeing the zero_grad effect concretely
PyTorch itself isn't available in this environment, but the accumulation bug doesn't actually depend on autograd internals, only on whether a number gets reset to zero between steps or not, so the same arithmetic is easy to show directly in plain Python and actually run:
def local_grad(w, target):
# dL/dw for L = (w - target) ** 2 -> 2 * (w - target)
return 2 * (w - target)
w = 0.5
targets = [1.0, 1.0, 1.0] # same target each "batch" on purpose, to isolate
# the accumulation effect from any real data change
print("With optimizer.zero_grad() called every step (correct):")
for step, y in enumerate(targets, start=1):
grad = 0.0 # zero_grad() resets the accumulator to 0 here
grad += local_grad(w, y)
print(f" step {step}: grad = {grad}")
print("With optimizer.zero_grad() missing (bug):")
grad = 0.0 # only initialized once, before the loop, never reset inside it
for step, y in enumerate(targets, start=1):
grad += local_grad(w, y) # this is what loss.backward() does to .grad
print(f" step {step}: grad = {grad}")
This was run exactly as shown and prints:
With optimizer.zero_grad() called every step (correct):
step 1: grad = -1.0
step 2: grad = -1.0
step 3: grad = -1.0
With optimizer.zero_grad() missing (bug):
step 1: grad = -1.0
step 2: grad = -2.0
step 3: grad = -3.0
w and the target are held fixed across all three steps here purely so the only thing changing is whether the accumulator resets, in a real loop w also updates every step, but the mechanism is the same one PyTorch's .grad accumulation follows: without a reset, .backward() keeps adding to whatever .grad already held, so by step 3 the optimizer would be reacting to a gradient three times too large, even though nothing about the data changed. That's the concrete shape of "training still runs, it just learns something subtly wrong."
Before running on real data
I run a single small batch through the loop first and watch whether loss decreases at all over a handful of steps on that tiny batch. That is a cheap, fast signal that the optimization path is wired correctly (gradients are flowing, the loss is actually connected to the model's parameters) before spending real time and compute on the full dataset.
Trade-offs and pitfalls
None of these mistakes throw an error, which is exactly what makes them dangerous: the loop runs to completion, the loss number exists, and everything looks fine on the surface while the model quietly learns worse than it should, or learns something that will not generalize. A training run finishing without a crash is not evidence the loop is correct.
How would you test AI-generated training or inference code for nondeterminism, hidden data leakage, and silent failure modes that may only appear after deployment?
Sample Answer
Direct answer
I would test this at three levels: unit tests that check determinism and leakage directly, integration tests that mimic realistic production data shapes, and production monitoring that catches the failure modes no offline test can fully replicate. The reason a dedicated pass is needed for AI-generated code specifically is that it can be syntactically correct and pass a naive test suite while still being statistically wrong, and that gap does not show up as a crash.
Structured elaboration
Nondeterminism. Fix every source of randomness explicitly: Python's own random module, NumPy, the ML framework's RNG (random number generator), and any data-loader shuffling. A useful concrete check is running the same job twice with the same seed and asserting the outputs match exactly, and once with a different seed to confirm you would actually notice if determinism silently broke:
import random
def run_trial(seed):
random.seed(seed)
# stand-in for "train a tiny model and return a summary statistic"
weights = [random.random() for _ in range(5)]
return sum(weights)
trial_a = run_trial(seed=42)
trial_b = run_trial(seed=42)
trial_c = run_trial(seed=7)
print("Trial A (seed=42):", trial_a, flush=True)
print("Trial B (seed=42):", trial_b, flush=True)
print("Trial C (seed=7): ", trial_c, flush=True)
print("A == B (same seed reproduces exactly):", trial_a == trial_b, flush=True)
print("A == C (different seed, expected to differ):", trial_a == trial_c, flush=True)
Running this prints:
Trial A (seed=42): 1.8991488243625052
Trial B (seed=42): 1.8991488243625052
Trial C (seed=7): 1.73393470277175
A == B (same seed reproduces exactly): True
A == C (different seed, expected to differ): False
That is the pattern to assert in an actual test: same seed produces bit-identical output, different seed does not. The stand-in computation here is deliberately simple; the same assertion pattern applies whether the thing being reproduced is five random numbers or a full training run.
Data leakage. Validate that train, validation, and test splits are separated by entity and time, not only by random row assignment, since a customer or a time window appearing on both sides of a split is a leak a purely random-assignment check would never catch. Add lineage checks so any label-derived or post-event column is flagged if it appears among the model's inputs, and add schema tests asserting that columns which should not exist at inference time are absent from the inference-time feature set.
Silent failures. Add canary tests using inputs shaped like real production edge cases: a batch that is entirely missing values in one column, out-of-range categorical values the model never saw during training, and degenerate inputs (all zeros, a single row). Compare the model's output distribution against a known baseline and set drift thresholds that would trigger an alert if predictions start looking meaningfully different from what training-time evaluation predicted. Log model version, a hash of the feature set, and confidence statistics on every request, so a silent failure in production leaves a trail to investigate rather than nothing at all.
After deployment. Monitor latency, error rate, prediction distribution skew, and the actual downstream business metric the model is supposed to move. Build an explicit rollback trigger tied to a sudden, sustained drop in one of these signals, rather than relying on someone noticing manually.
Worked example
A concrete silent-failure scenario: an AI-generated inference function silently coerces an unrecognized categorical value to a default rather than raising an error. Offline, this never triggers, because the test data only contains categories seen during training. In production, three months later, a new product category is introduced upstream, and every prediction for that category silently uses the default encoding instead of a meaningful one, degrading accuracy for that slice with no error, no log line, and no obvious signal until someone notices the business metric drifting for that segment specifically. A canary test with an explicitly unseen category, asserted to either raise or flag rather than silently default, would have caught this before deployment.
Trade-offs and pitfalls
A test suite this thorough takes real engineering time to build and maintain, and it is tempting to skip the entity/time-aware leakage checks in favor of the faster, purely random-split tests that are easier to write. The trade-off is that the easier tests give false confidence precisely on the failure modes that matter most, since a model can pass every naive test and still be wrong in a way that only shows up as a slow, hard-to-diagnose accuracy decay after deployment.
Unlock Full Question Bank
Get access to all 17 Version Control and Developer Tooling interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.