CI/CD questions at the lead and staff level aren't really about which tool you use — they're about how you reason under the central tension of shipping fast without shipping broken: how a pipeline enforces that tension automatically instead of relying on someone remembering to check, how a branching strategy trades merge pain for release flexibility, and how a rollback plan holds up when the thing that broke wasn't the code but the data underneath it. Interviewers use these questions to find engineers who have actually owned a deployment pipeline through a real incident, not just configured one that has never been tested under pressure. This page works through pipeline design, trunk-based development versus GitFlow, blue-green and canary releases, feature flags, database changes in a pipeline, rollback strategy and the DORA metrics that measure whether any of it is actually working.

Q1 Design a CI/CD pipeline for a .NET microservice from commit to production. What gate exists at each stage, and why does "build once, deploy many" matter?#

Short answer: A pipeline should build exactly one immutable, versioned artifact per commit — a container image or deployable package — and promote that same artifact unchanged through test, staging and production, because rebuilding separately at each stage reintroduces the risk that what you tested isn't bit-for-bit what you ship; each stage's gate exists to catch a specific class of failure as cheaply and as early as possible, from a compile error at commit through a load-bearing health check minutes after a production deploy.

The sequence I design toward is: commit triggers build plus unit tests for fast feedback measured in minutes; static analysis and dependency vulnerability scanning next, since they're still cheap and catch a different class of problem than tests do; package and tag the artifact with an immutable identifier — a git SHA or semantic version, never latest — and push it to an artifact registry; deploy that exact artifact to an integration environment and run tests against the real thing, not a rebuild of it; gate promotion to staging behind either automated checks or a deliberate human approval; and only then move to a progressive production rollout — canary or blue-green — with automated, metrics-gated promotion and a post-deploy smoke test. "Build once, deploy many" specifically prevents the class of bug where staging passes and production fails because a separate build picked up a different transitive package version or a slightly different base image layer — without a pinned lockfile and a single promoted artifact, "tested" and "shipped" are quietly two different binaries. Environment-specific values — connection strings, feature flag endpoints, scaling parameters — have to be injected at deploy time through configuration, never baked into the artifact, which is precisely what keeps one build valid across every environment it's promoted through; the same pipeline discipline underpins CI/CD for .NET with GitHub Actions and Azure DevOps.

YAML
jobs:
  build-test:
    steps: [checkout, restore, build, test]
  scan:
    needs: build-test
    steps: [dependency-scan, static-analysis]
  package:
    needs: scan
    steps: [docker-build-tag-sha, push-registry]
  deploy-staging:
    needs: package
    steps: [deploy, integration-tests]
  deploy-production:
    needs: deploy-staging
    steps: [canary-deploy, automated-analysis, promote-or-rollback]

What interviewers look for: an ordered, fail-fast gate sequence with a stated reason for each stage, plus explicit "build once, deploy many" reasoning rather than a list of tool names.

Common mistakes: rebuilding the artifact separately for each environment, which lets environment-specific drift slip past everything that was actually tested.

Q2 Compare trunk-based development and GitFlow. When, if ever, would you still choose GitFlow for a .NET codebase?#

Short answer: Trunk-based development — short-lived branches, often merged within a day, with incomplete work hidden behind feature flags instead of a long-lived branch — is the better default for a continuously deployed service because merge conflict cost grows non-linearly with branch lifetime, so integrating constantly keeps that cost low; GitFlow's long-lived develop/release/hotfix branches still earn their keep for software that ships discrete, versioned releases customers install and run on their own schedule, where you genuinely need to patch an old version independently of ongoing development on the next one.

The mechanical argument for trunk-based development isn't philosophical, it's that a handful of long-lived branches accumulate divergence from main continuously, and the eventual merge cost compounds with every day they stay open; many short-lived branches integrating constantly avoid that compounding almost entirely. GitFlow was designed for a release cadence where "cut a release branch, stabilize it, ship it, hotfix it independently" made sense because releases were infrequent and installed rather than continuously deployed. The place it still fits a .NET codebase is a shipped product with multiple supported major versions in the field simultaneously — a NuGet library, an on-premises enterprise product, a SaaS platform with contractually pinned upgrade windows for large customers — where a real branch has to represent "what's running in production for version N" independent of "what's being built for version N+1," and a hotfix needs to land there without dragging in unrelated changes already merged to main. Trunk-based development's hard prerequisite is feature flags: you can't safely merge incomplete work into a line that deploys continuously without a way to hide it from users, so a team adopting the branching model without an established flagging discipline usually reinvents long-lived branches under a different name within a few months.

What interviewers look for: the merge-cost mechanic as the real justification for trunk-based development, and a specific, real scenario for GitFlow (multiple supported versions in the field) rather than "GitFlow is outdated" stated as an unqualified opinion.

Common mistakes: recommending trunk-based development without acknowledging its feature-flag prerequisite, or dismissing GitFlow entirely without considering shipped, versioned software.

Q3 Compare blue-green and canary deployments. How do you decide which fits a given service, and what actually limits how fast you can roll back each one?#

Short answer: Blue-green keeps two full production environments and switches all traffic from the old to the new version at once, giving a fast, environment-level rollback by switching back — at the cost of running roughly double the infrastructure, at least briefly; canary shifts a small percentage of real traffic to the new version, watches it against defined health signals, and progressively increases that share, which limits the blast radius of a bad release at the cost of a slower, metrics-gated rollout and the added complexity of running two versions concurrently at partial scale for longer.

The decision driver I actually use is blast-radius tolerance versus infrastructure cost: blue-green earns its doubled infrastructure cost for services where a fast, clean, all-or-nothing rollback matters more than limiting exposure during rollout — often lower-traffic or architecturally simpler services where briefly doubling cost is cheap in absolute terms. Canary fits high-traffic services better, where even a few minutes of a bad release at 100% of traffic is expensive, and where the team has the observability maturity to gate promotion on real signals rather than a timer. The rollback-speed nuance interviewers are probing for: blue-green's rollback is fast for the application but doesn't help at all if the bad release already wrote bad data — switching traffic back to the old environment doesn't fix a schema or data-corruption bug if both environments read from the same database the new version just corrupted, which is the same "rollback isn't really instant" trap that shows up whenever shared state is involved. Canary's rollback is typically faster in practice for genuinely bad releases, because an automated gate can halt and revert at 5% traffic before most users are ever affected — but that speed only exists if the health signals and thresholds were defined correctly in advance; a canary gated only on "is the pod up" catches almost nothing a readiness probe wasn't already catching.

What interviewers look for: blast-radius-versus-cost framed as the actual decision driver, and the specific insight that application rollback speed isn't the same as rollback safety once shared data has already been mutated.

Follow-up questions:

  • What health signals would you gate an automated canary promotion on for a payments API specifically?
  • How would you run blue-green for a service with one shared database, where "two environments" doesn't mean two databases?

Q4 What categories of feature flags are worth distinguishing, and how do you keep a flagging system from becoming its own source of production risk?#

Short answer: Release flags (temporary, gate incomplete work, meant to be deleted once fully rolled out), ops flags (kill switches for degrading gracefully under load or a bad dependency), permission flags (long-lived, gate features by entitlement or plan) and experiment flags (drive A/B tests) behave differently enough that treating them the same is where flag systems go wrong; the discipline that actually prevents flag debt is treating release flags specifically as debt with an expiration date — tracked, owned and deleted — because a codebase with hundreds of stale conditionals nobody remembers the purpose of is measurably harder to reason about and test than the branches it replaced.

In .NET, Microsoft.FeatureManagement gives you IFeatureManager.IsEnabledAsync(featureName) as the check, and pairing it with Azure App Configuration's feature flag store lets flags be toggled centrally without a redeploy — the property that makes ops flags genuinely useful as an incident response tool, since flipping a flag takes seconds, not a full pipeline run. The combinatorial risk worth naming unprompted: N independent flags create up to 2^N possible code paths in theory, and a test suite that only ever exercises "all flags at their default" is testing a small fraction of what's actually running in production once flags accumulate; the practical mitigation is keeping the number of concurrently active, interacting release flags small, removing them promptly once fully rolled out, and explicitly testing the handful of combinations that can realistically coexist rather than chasing every combinatorial possibility. Ops flags and permission flags are architecturally different and shouldn't be swept into the same cleanup discipline — they're meant to live indefinitely and deserve to be tested as first-class configuration, not treated as debt.

C#
if (await featureManager.IsEnabledAsync("NewCheckoutFlow"))
{
    return await newCheckout.ProcessAsync(order, cancellationToken);
}
return await legacyCheckout.ProcessAsync(order, cancellationToken);

What interviewers look for: the category distinction — especially release-flag-as-debt versus ops/permission-flag-as-permanent-configuration — and a real answer to the combinatorial testing risk, not just "we use feature flags."

Common mistakes: letting release flags accumulate indefinitely with no owner or removal plan, until the codebase is effectively running an untested configuration matrix.

Q5 How do you handle database schema changes in a pipeline so they don't block, or get blocked by, a rolling application deployment?#

Short answer: Use the expand/contract pattern — first ship an additive, backward-compatible schema change (expand) that both old and new application code can run against simultaneously, then deploy the application code that uses it, then ship a later, separate contract step that removes what's no longer needed — and run migrations as their own gated pipeline step ahead of the application deployment, not embedded in application startup, so a slow or failing migration doesn't get triggered redundantly by every pod trying to run it at once.

A column rename, done safely, is actually three separate deploys: add the new column (expand); deploy code that writes to both and reads from the new one with a fallback; backfill existing data; then drop the old column (contract) once nothing reads it anymore. Done this way, a rolling update where old and new pods briefly coexist never has a pod running against a schema shape it doesn't understand — the same "old and new versions serve traffic concurrently" constraint that makes Kubernetes rolling updates safe only when the underlying schema tolerates both versions at once. Running EF Core migrations as a dedicated pipeline step — a migrations bundle executed by the pipeline, or a Kubernetes Job — rather than calling Database.Migrate() at application startup avoids the failure mode of N pods starting concurrently and racing to apply the same migration, and it lets the deployment gate explicitly on migration success instead of discovering a migration failure buried inside one pod's crash loop. A destructive-looking change — dropping a column, narrowing a type — should never ship in the same release as the code that stops needing the old shape; that coupling is exactly what expand/contract exists to break apart, trading one large, risky deploy for several small, independently reversible ones.

Bash
dotnet ef migrations bundle --project Orders.Data --configuration Release --output ef-bundle
./ef-bundle --connection "$PRODUCTION_CONNECTION_STRING"

What interviewers look for: expand/contract explained as a sequence of independently safe deploys, and migrations run as a controlled, gated step rather than implicitly at application startup.

Common mistakes: combining a destructive schema change with the application code that depends on it in a single deploy, which removes the ability to roll back the code without also losing data.

Q6 What does "rollback" actually mean for a stateful service, and when does redeploying the previous version not actually fix the problem?#

Short answer: Rolling back application code is fast and well understood — redeploy the previous artifact, or flip a feature flag if the bad behavior is flag-gated — but it doesn't undo anything the bad release already did to shared state: data written in an incompatible shape, a destructive migration that already ran, a message already published with a new schema, or an external side effect like a charge or an email already sent; for all of those, the only real fix is rolling forward with a corrective change, which is why "prefer roll-forward" is more than a platitude for anything touching persistent state.

The decision tree I use during an incident: if the bad behavior is purely in application logic with no persisted side effect yet, redeploy or flip the flag — fast and clean. If it has started writing data in an incompatible way, stop the bleeding first — flag off, or scale to zero — before assessing whether the already-written data needs a corrective migration, because redeploying old code against already-mutated data can make things worse, not better. If it has triggered an external side effect, no code-level rollback fixes an email that already sent; that needs business or support-level remediation, not a git revert. Feature-flag-based rollback is why release flags earn their complexity in the first place: flipping a flag is seconds and needs no rebuild, while redeploying the previous artifact requires at least the deploy stage of the pipeline to run again — minutes, not seconds, precisely when minutes matter most. This connects directly to the DORA "failed deployment recovery time" metric covered next: a team's actual recovery time is bounded by the slowest lever in this decision tree, which is usually the redeploy path, not the flag-flip path, so gating your riskiest changes behind flags measurably improves that number.

What interviewers look for: explicit acknowledgment that "rollback" doesn't undo persisted side effects, plus a real decision tree that starts with "stop the bleeding" rather than jumping straight to redeploying the previous version.

Follow-up questions:

  • How do you decide, during an active incident, whether it's safer to roll back or roll forward?
  • What telemetry would tell you a migration already caused data corruption before a customer tells you?

Q7 What are the four DORA metrics, and what's the actual trap in optimizing deployment frequency without watching change failure rate at the same time?#

Short answer: Deployment frequency, lead time for changes (commit to production), change failure rate (the share of deployments that cause a production failure) and failed deployment recovery time (how fast service is restored after a failure) are the four keys; the trap is that frequency and failure rate can move in opposite directions by accident — a team can post an impressive deployment frequency by shipping many small, low-risk changes while quietly avoiding the larger changes the business actually needs, or by relaxing review and testing rigor to push the count up, which shows up later as a worse change failure rate.

These four exist together specifically because throughput metrics (frequency, lead time) and stability metrics (failure rate, recovery time) can trade against each other, and a team optimizing only one pair is very likely gaming the numbers rather than genuinely improving delivery — the underlying research finding is that elite-performing teams improve throughput and stability together, not one at the other's expense, because the practices that improve both (small batch sizes, automated testing, trunk-based development paired with feature flags, fast automated rollback) are largely the same practices. In practice I treat the four as one dashboard, never read in isolation: a rise in deployment frequency alongside a flat or improving change failure rate is real progress, while the same rise alongside a climbing failure rate means the team is trading stability for a vanity metric, and that's worth surfacing before it becomes an incident pattern. Lead time for changes is the metric most directly under a single team's day-to-day control — it's shortened by exactly the pipeline discipline from the first question on this page: fast feedback stages, build-once-deploy-many, automated gates instead of manual approval queues — which is often the highest-leverage place to invest first.

What interviewers look for: all four metrics named correctly, and specifically the insight that they have to be read together — improving one while ignoring another isn't the same as improving delivery.

Common mistakes: treating deployment frequency as the metric that matters most in isolation, without connecting it back to change failure rate.

Q8 How do you secure a CI/CD pipeline itself — the deployment credentials, the build artifacts, the supply chain — not just the application it deploys?#

Short answer: Eliminate long-lived, stored deployment credentials by using OIDC federation, so the pipeline authenticates to Azure with a short-lived token issued per run instead of a secret sitting in pipeline configuration; scope every pipeline's deployment identity to only the environment it's actually allowed to touch, so a compromised pipeline definition for a staging deploy can't reach production; and treat the build artifact itself as something needing provenance — know exactly what commit, what dependencies and what pipeline run produced the binary currently running in production.

A stored service principal secret or long-lived deployment key in pipeline configuration is one of the highest-value targets for an attacker, because compromising it gets direct write access to production; OIDC federation removes that stored secret entirely, since the token is minted per run and expires quickly. Least-privilege scoping matters as much as the credential type: a single pipeline identity with broad "contributor on the subscription" access defeats the purpose of environment separation even if it's OIDC-based, so each environment's deployment identity should be scoped to only that environment's own resource boundary. Supply chain concerns extend past the pipeline's own credentials to what it pulls in and produces — dependency scanning against known vulnerabilities, verifying package signatures where available, and generating a software bill of materials for what actually shipped, not primarily to satisfy an audit checkbox, but because the fastest way to answer "are we affected by this newly disclosed vulnerability in package X" during an active incident is a queryable SBOM, not a manual search across every service's dependency tree under pressure.

YAML
permissions:
  id-token: write
  contents: read
steps:
  - uses: azure/login@v2
    with:
      client-id: ${{ vars.AZURE_CLIENT_ID }}
      tenant-id: ${{ vars.AZURE_TENANT_ID }}
      subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

What interviewers look for: OIDC federation named specifically as the fix for stored deployment secrets, plus least-privilege scoping per environment and a real reason SBOMs matter beyond compliance.

Common mistakes: treating pipeline security as solely "scan the application code" while long-lived, broadly scoped deployment credentials sit unnoticed in pipeline configuration.

Q9 Design the automated promotion gate for a canary release of a payments API. What do you measure, and how long do you bake before promoting to full traffic?#

Short answer: Gate on a small set of signals that directly reflect customer harm — error rate and latency percentiles compared against the stable baseline version serving the rest of traffic concurrently, not a fixed historical threshold — plus at least one business-level signal like payment authorization success rate, and bake each traffic step long enough to cover real variance for that step's volume, which for most services means minutes to the low tens of minutes, not one arbitrary timer applied uniformly regardless of what's being tested.

Comparing against the concurrently running stable baseline, rather than a historical average, is the key design choice, because it controls for time-of-day and load-composition differences that a fixed historical threshold would misread as a regression — a canary that looks "bad" only because traffic composition genuinely differs at that hour from the daytime baseline it's compared against produces false rollbacks that erode trust in the automation over time. For a payments API specifically, error rate alone is an insufficient signal: a "successful" HTTP response that silently fails to actually authorize a payment is worse than a loud error, so the gate needs a domain-specific correctness signal — authorization success rate, or a reconciliation match rate against the payment processor — alongside the generic ones. Bake time should scale with the traffic step size rather than being one flat number: a 1% step needs enough real volume flowing through it before the comparison is statistically meaningful, which for a lower-traffic endpoint might mean holding that step longer than a textbook 10 to 15 minutes, while a high-traffic endpoint may have enough signal in well under five. The gate should default to abort-and-roll-back on ambiguous data, never to promote — a canary analysis that can't distinguish "healthy" from "not enough data yet" should never treat silence as a pass.

YAML
canary:
  steps: [ { weight: 5 }, { pause: { duration: 10m } },
           { weight: 25 }, { pause: { duration: 10m } },
           { weight: 100 } ]
  analysis:
    metrics:
      - name: error-rate
        successCondition: result <= baseline.error_rate * 1.1
      - name: p99-latency
        successCondition: result <= baseline.p99_latency_ms * 1.2

What interviewers look for: baseline-relative comparison instead of fixed historical thresholds, a domain-specific correctness signal beyond generic HTTP metrics for a payments context, and "abort on ambiguous data" as the default.

Follow-up questions:

  • How would this gate design change for a service where traffic is too low for statistical significance at a 1% canary step?
  • What's your plan when the automated gate and a human on-call engineer disagree about whether to promote?

Q10 Walk through how you'd handle a production deployment that's actively causing customer-visible errors, from the first alert to the postmortem.#

Short answer: Stop new harm first — flag off, scale to zero, or roll back, whichever is fastest for this specific failure, in that order of preference — communicate status before you fully understand root cause, and only once the immediate harm is contained do you invest time in understanding exactly what broke; the postmortem afterward should change a system or a gate, not just document what a human should remember to do differently next time.

The first minutes are about mechanism, not diagnosis: if the change is behind a feature flag, flip it off — seconds; if it isn't, and the previous artifact is known-good, redeploy it, keeping in mind that this only fixes application behavior, not any data damage the bad release already caused; if neither is fast enough given the blast radius, scaling the bad deployment to zero and accepting reduced capacity can buy time without guessing at a fix under pressure. Communication runs in parallel with mitigation, not after it — stakeholders and support teams need "we know, we're mitigating, next update in N minutes" well before the team has a root cause, and treating communication as a distraction from the real work is a common mistake that costs trust exactly when it matters most. Root cause analysis happens once the bleeding has stopped, and it should trace back to why the pipeline's existing gates didn't catch this: did the canary analysis's metrics not cover this failure mode, was the change not behind a flag when it plausibly could have been, did staging lack production-representative data or load to surface the issue at all. The postmortem's output has to be a change to the system — a new automated check, a new canary metric, a flag requirement added to a category of change that previously shipped ungated — because a postmortem whose only action item is "be more careful" all but guarantees a repeat incident, and is exactly the kind of outcome that keeps change failure rate and recovery time from improving release over release.

What interviewers look for: a clear "stop harm, then communicate, then diagnose, then fix the system" ordering, with postmortem output framed as a change to tooling or process, not a request for more vigilance.

Common mistakes: spending the first, most time-critical minutes trying to fully understand root cause instead of mitigating customer impact with the fastest available lever.

Quick-Fire Round#

QuestionAnswer
What's the core idea behind "build once, deploy many"?Promote the exact same artifact through every environment instead of rebuilding per stage.
What does trunk-based development require to stay safe?Feature flags to hide incomplete work merged into main.
Blue-green's rollback advantage in one line?An instant, environment-level switch back to the previous version.
What does expand/contract avoid during a rolling deploy?A pod running old or new code against a schema it doesn't understand.
Where should EF Core migrations run in a pipeline?As their own gated step, not inside application startup.
What's the fastest rollback lever for flag-gated behavior?Flipping the feature flag off — no redeploy required.
Name the four DORA metrics.Deployment frequency, lead time for changes, change failure rate, failed deployment recovery time.
What replaces a stored pipeline deployment secret?OIDC federation issuing a short-lived token per run.
Should a canary gate compare against history or a live baseline?A live, concurrently running baseline.
What should a postmortem's action items change?A system or gate — not just "be more careful."

How to Prepare#

  • Be able to describe a full pipeline stage by stage, naming the specific failure each gate is meant to catch.
  • Know the merge-cost argument for trunk-based development, and one real scenario where GitFlow still fits.
  • Practice the blue-green-versus-canary decision in terms of blast radius and infrastructure cost, not just "canary is newer."
  • Rehearse expand/contract as a named pattern, with a concrete example like a column rename.
  • Memorize the four DORA metrics cold, plus the specific trap of optimizing one without the others.
  • Prepare one real incident story with a "stop harm, then diagnose" structure and a postmortem action item that changed a system.