Organize the answer around ownership, limits, failure, and recovery. Definitions become interview-ready when they survive a concrete production scenario.
Question set
8 detailed answers
01Design a CI/CD pipeline for an org with ~50 microservices. Walk through the stages and tradeoffs.
senior
Short answer: One shared pipeline template for all services; build once → an immutable artifact (image digest) promoted through environments without rebuilding; tests in tiers from fast to expensive; canary with auto-rollback in prod. You are graded on how you argue the tradeoffs, not on the diagram.
In depth:
PR: lint + unit (< 5 min, merge gate)
│ merge to main
▼
build ──► immutable artifact (image digest) ──► registry
▼
integration (contract tests, mocked neighbors)
▼
staging: e2e + smoke ──► promote the SAME artifact
▼
prod: canary 5% ──► metrics ok ──► 100% (else auto-rollback)
- Build once, promote everywhere — rebuilding per environment means you tested one binary and shipped another. Promote a digest, not a
latesttag. - Test tiers — a fast gate on the PR, expensive e2e after merge: otherwise the merge queue grows to hours.
- Shared template — 50 services × hand-rolled YAML = drift and a zoo; a parameterized shared template updates centrally.
- Secrets — OIDC federation into cloud IAM, short-lived tokens instead of static keys stored in CI.
⚠️ Common mistake: listing stages with no tradeoffs. The interviewer is listening for speed vs safety: which tests you cut where, what blocks merge vs what only blocks the deploy.
02Trunk-based development vs GitFlow: which model enables continuous deployment, and why?
middle
Short answer: Continuous deployment is only real with trunk-based + feature flags: small batches, branches that live hours, main always deployable. GitFlow with long-lived develop/release branches means big batches and merge hell; its home is boxed, versioned software.
In depth:
| Trunk-based | GitFlow | |
|---|---|---|
| Branches | main + branches < 1–2 days | develop, release/, hotfix/ — long-lived |
| Batch size | small, integrate daily | large, integrate at release time |
| Deploy vs release | decoupled via feature flags | glued together: release = merging the release branch |
| Fits | SaaS, continuous deployment | boxed software, several supported versions |
- Flags decouple deploy from release — code ships to prod turned off; turning it on is config, not a deploy. That removes the fear of merging unfinished work into main.
- The argument is batch size, not taste: the longer a branch lives, the more conflicts, the longer the lead time, the scarier the rollback — DORA metrics are exactly about this.
⚠️ Common mistake: answering "whatever the team prefers." It is not taste: long branches → big batches → long lead time — a measurable argument.
03Canary deployment: how do you automate the promote-or-rollback decision?
senior
Short answer: Automated canary analysis: at each ramp step, compare the canary against a baseline of the same size (not the whole fleet!) on SLI metrics — error rate, latency percentiles — over a 5–10 minute window; crossing a threshold triggers auto-rollback with no human involved. This is how Argo Rollouts and Flagger work.
In depth:
traffic: 1% ──► 5% ──► 25% ──► 100%
│ │ │
▼ ▼ ▼
analysis window at every step:
canary vs BASELINE (fresh pods of the OLD version,
same replica count):
• error rate ≤ baseline + threshold
• p99 latency ≤ baseline × 1.05
any step fails ──► auto-rollback + alert
- Same-size baseline — comparing one canary pod against 50 warmed-up old ones is unfair: different cache warmth and connection pools. That is why Flagger/Argo spin up a fresh baseline from the old version.
- Metrics = SLIs, not CPU: users do not care about utilization; errors and latency matter — plus a business metric if you have one (checkout conversion).
- Auto-rollback is the whole point: a canary without an automated decision is just a slow deploy someone has to babysit.
⚠️ Common mistake: stopping at the definition "send 5% of traffic." The question is about the analysis: what you compare against what, with which thresholds, and who pulls the rollback trigger.
04What is GitOps, and why is the pull model (ArgoCD/Flux) better than CI pushing to the cluster?
middle
Short answer: GitOps: git is the single source of desired state, and an agent inside the cluster continuously reconciles actual state toward it. Pull beats push for two reasons: cluster credentials never leave the cluster, and you get continuous reconciliation — drift is detected and healed, not just at deploy time.
In depth:
| Push (CI → cluster) | Pull (ArgoCD/Flux) | |
|---|---|---|
| Credentials | admin kubeconfig sits in CI | agent inside; nothing handed out |
| Drift | a manual kubectl edit goes unnoticed |
detect + self-heal automatically |
| Audit | CI run logs | git log — full "who changed what" history |
| Rollback | rerun an old pipeline | git revert |
| Scale | every pipeline knows about the cluster | app-of-apps, new clusters added declaratively |
- CI builds, CD reconciles — the pipeline publishes the image and commits the new tag to the config repo; the agent does the rest.
- Separate config repo — code and manifests live apart: otherwise every image bump triggers the application's own CI.
⚠️ Common mistake: "GitOps = keeping YAML in git." Without continuous reconciliation and self-heal it is just configs in a repository.
05How do you handle secrets in CI pipelines and in GitOps repositories?
middle
Short answer: Never plaintext in git, even in a private repo: history, forks, and backups live forever. In CI — OIDC federation into cloud IAM: a short-lived token per job instead of stored keys. On the GitOps side — SOPS/sealed-secrets (ciphertext in git) or external-secrets (git holds only a reference to Vault/Secrets Manager).
In depth:
| Layer | Solution | Why |
|---|---|---|
| CI → cloud | OIDC federation (runner → IAM role) | no stored keys; the token lives minutes — nothing to steal or rotate |
| CI variables | masked/protected vars — bare minimum only | a static secret, visible to maintainers, leaks into logs (base64 defeats masking) |
| GitOps repo | SOPS / sealed-secrets | ciphertext is safe to commit; decryption only via a key inside the cluster |
| Reference, not value | external-secrets operator | git stores a pointer, the value lives in Vault; rotation never touches git |
- Rotation is part of the answer: where a secret is born, who changes it and how, what gets redeployed afterwards. Without that, "we encrypt" is not an answer.
- A leaked secret is compromised forever — git history is not cleaned by "deleting the file"; only revoke and reissue.
⚠️ Common mistake: "we use GitLab masked variables" as the whole answer — that is a long-lived static secret nobody rotates.
06CI builds take 40 minutes. How do you attack that?
middle
Short answer: Profile first — break the time down by stage and find where it actually sits, instead of buying runners. Then in descending payoff order: a parallel DAG instead of a chain, Docker layer caching (dependencies before source), a shared remote cache, test sharding, affected-only builds in a monorepo. Hardware comes last.
In depth:
- Measure — stage timings are already in your CI: usually 80% of the time sits in 1–2 places (tests or the docker build).
- DAG instead of a chain — lint, unit tests, and image builds are independent → run them in parallel; the critical path shrinks.
- Dockerfile layer order —
COPYthe dependency manifest and install BEFORECOPY . .: a code edit no longer invalidates the dependency layer. - Remote cache — ephemeral runners without a shared cache rebuild the world every time: buildx cache, package-manager caches.
- Test sharding — split by historical timings across N parallel jobs.
- Affected-only — in a monorepo, build only the touched packages (nx / turborepo / bazel).
- Bigger runners last — paying with hardware before profiling just masks the problem.
⚠️ Common mistake: starting with "buy bigger runners." If 30 of the 40 minutes are sequential e2e tests, hardware buys you minutes; parallelization and caching buy tens.
07How do database migrations fit into zero-downtime deploys and rollbacks?
senior
Short answer: The expand/contract pattern: first an additive migration (new column/table), then code compatible with both schemas, then a backfill — and only later the contract step (dropping the old) as a separate change. Schema and code change in separate deploys, and every step stays backward-compatible by one version — otherwise instant code rollback is impossible.
In depth:
T0 expand: ADD COLUMN new (nullable/default) — schema only
T1 deploy v2: writes both columns, reads new → fallback old
T2 backfill old rows in batches (not one giant UPDATE!)
T3 deploy v3: reads and writes only new
T4 contract: DROP COLUMN old — once rollback to v2 is off the table
invariant: at any point, code versions N and N−1
both work against the current schema
- Why not lock-step — "migration + code in one deploy" breaks twice: during the rollout, old pods already see the new schema, and rolling back the code requires rolling back the schema — i.e. losing data.
- Rename is also expand/contract — add → dual-write → backfill → drop; a direct
RENAME COLUMNkills the running version. - Backfill separate from the migration — a giant UPDATE at deploy time holds locks and takes prod down; do it in batches, in the background.
⚠️ Common mistake: putting DROP/RENAME in the same release as the code. Five boring steps — but rollback stays a button, not a restore from backup.
08A bad release reached prod even though all tests were green. What do you change in the system?
middle
Short answer: Accept that tests never catch everything, and build out the AFTER-deploy loop: smoke tests and SLI gates in prod, canary with auto-rollback, a staging ↔ prod parity audit, feature flags on risky paths. And a blameless review of the pipeline itself: the delivery system failed, not the engineer.
In depth:
- Post-deploy verification — smoke tests and SLI checks right after the rollout, as a pipeline stage; "deploy and eyeball the dashboard" is not a gate.
- Canary + auto-rollback — a bad release gets 5% of traffic and rolls back in minutes: a bounded blast radius instead of all-or-nothing.
- Staging/prod parity — green tests on a dissimilar environment prove nothing: data, configs, dependency versions, load.
- Feature flags — a risky path gets switched off via config in seconds, with no new deploy.
- Blameless postmortem of the pipeline — the question is "which gate should have caught this and why doesn't it exist," not "who is to blame."
⚠️ Common mistake: answering "add more tests." Tests live before prod; the class of bugs that only appear in prod (data, load, config) is caught only by the after-deploy loop.
Source notes
References and review policy
RecallDeck’s interview answers are editorial material, reviewed against maintained official documentation where a primary reference is available. Tool selections use direct provider links and contain no affiliate placements. Features can change after the review date.
From reading to recall
Practice the full interview loop.
RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.