Code Review and Working with Existing Codebases Questions
Reviewing others' code and navigating unfamiliar systems: giving and receiving actionable review feedback, spotting correctness and design issues, and reading and understanding large or legacy codebases before changing them. Covers collaborative coding norms, incremental change in shared repositories, and verifying changes against existing behavior. The team-facing side of day-to-day engineering.
You are reviewing automation code that performs TLS certificate rotation for internal services. Identify failure modes, security checks, and test cases you would require. Propose a robust design for rotation that avoids downtime, supports emergency rollback, and ensures private key secrecy during rotation.
Sample Answer
Direct answer
Review this as three linked concerns: what can go wrong (failure modes), what has to always be true for security (checks), and what has to be provably tested before this runs against real services. Design the rotation itself around a staged, node-by-node rollout that avoids downtime, keep the old certificate valid in parallel until every node is confirmed on the new one, support a fast, tested rollback, and keep private key material out of the automation's own logs, disk, and version control at every step.
Structured elaboration
TLS (Transport Layer Security) is the protocol that encrypts and authenticates network connections using a certificate and a private key.
Failure modes. Deploying an expired or not-yet-valid certificate, often from clock drift between the automation and the certificate authority. A partial rollout that leaves some instances on the old certificate and some on the new one, so clients see inconsistent trust depending on which instance they hit. A crash mid-rotation that leaves private key material readable on disk longer than intended. A failed service reload that causes real downtime instead of a clean handoff. Rolling back to a certificate whose private key may itself be the thing that's compromised, which isn't automatically safe.
Security checks. Private keys should be generated inside, and never leave, a KMS (key management service) or HSM (hardware security module, a dedicated device that generates and stores keys so the automation software never handles the raw key bytes). Every rotation run should validate the new certificate's chain, its expiry, and that its subject or SAN (subject alternative name, the field listing which hostnames the certificate is valid for) actually matches the service, before that certificate is deployed anywhere. Access to trigger a rotation should be restricted, and every rotation should be logged for audit.
Test cases. Unit tests for certificate parsing and validation logic. An integration test running the full pipeline against a staging certificate authority. A chaos test that kills a node mid-rollout and confirms the system recovers to a consistent state. Explicit negative tests for an expired, malformed, or revoked certificate, confirming each is rejected rather than silently deployed. A rollback test that intentionally deploys a bad certificate and confirms the automated rollback actually restores service.
Worked example
A concrete rollout design for a fleet of internal services behind a load balancer: generate the new key and certificate inside the KMS/HSM, so the automation only ever handles a reference or token, never raw key bytes. Validate the new certificate, chain, expiry, and SAN match, before touching any node. Roll it out node by node: deploy to one node, health-check it with an actual mutual-TLS (mTLS, TLS where both sides present a certificate) probe against that specific node, and only then move to the next. Keep the previous, still-valid certificate available in parallel throughout the rollout, so there's no window where a client can be rejected by both the old and the new certificate at once. If a health check fails partway through, stop and automatically roll the affected nodes back to the last-known-good certificate. Only after every node is confirmed on the new certificate does the automation retire the old one.
Trade-offs and pitfalls
A node-by-node rollout with health checks is slower than an all-at-once swap, but avoiding a fleet-wide outage from one bad certificate is the entire point of the design. Keeping the old certificate valid in parallel during rollout is the actual downtime-avoidance mechanism, and revoking it too early is the single most common way teams reintroduce the exact outage this design exists to prevent. If the rollback target's own key is the thing suspected of being compromised, rolling back to it isn't actually safe, that scenario needs a fresh emergency issuance instead, so the runbook needs to clearly separate "bad rotation, roll back" from "compromised key, reissue" as two different playbooks rather than one.
Technical coding: Given the following Python function used in a deployment script, write pytest unit tests that cover normal behavior and edge cases. Mock external API calls.
import requests
def get_latest_image(repo):
r = requests.get(f'https://registry.example/api/{repo}/latest')
r.raise_for_status()
return r.json()['tag']
Provide at least three tests and explain why you chose them.
Sample Answer
Direct answer
I'd write at least four tests, covering the success path, an HTTP error response, a malformed JSON body missing the expected key, and a network-level failure like a timeout, all with requests.get mocked so no test makes a real network call. Each test targets a distinct way this function can fail in production, not just variations on the happy path.
Structured elaboration
Approach. Mock requests.get so the function's own logic, not the network, is what's under test. For each test, build a fake response object with just enough behavior to drive the code path being tested (raise_for_status either does nothing or raises, json() returns a controlled payload), then assert on get_latest_image's return value or on the exception it raises.
Why these specific tests.
- Success proves the normal path works and the correct value is extracted from a realistic JSON payload.
- HTTP error (a 4xx or 5xx status) proves the function surfaces the failure via
raise_for_status()rather than silently returning something wrong. - Missing key in the response body proves that if the API's response shape doesn't match what the code expects, the caller gets a clear exception rather than a confusing downstream error somewhere else.
- Network-level failure (a timeout, a connection error) proves the function doesn't swallow or mask an infrastructure problem, which matters specifically because this function is used in a deployment script where a caller needs to know the difference between "the deploy image genuinely doesn't exist" and "we couldn't reach the registry at all."
Worked example
# deploy_utils.py
import requests
def get_latest_image(repo):
r = requests.get(f'https://registry.example/api/{repo}/latest')
r.raise_for_status()
return r.json()['tag']
# test_deploy_utils.py
from unittest.mock import Mock, patch
import pytest
import requests
from deploy_utils import get_latest_image
def make_response(json_data=None, raise_error=None):
resp = Mock()
resp.raise_for_status = Mock(side_effect=raise_error) if raise_error else Mock()
resp.json = Mock(return_value=json_data or {})
return resp
def test_get_latest_image_returns_tag_on_success():
resp = make_response(json_data={'tag': 'v1.2.3'})
with patch('deploy_utils.requests.get', return_value=resp) as mock_get:
result = get_latest_image('myapp')
assert result == 'v1.2.3'
mock_get.assert_called_once_with('https://registry.example/api/myapp/latest')
def test_get_latest_image_raises_on_http_error():
resp = make_response(raise_error=requests.exceptions.HTTPError('404 Client Error'))
with patch('deploy_utils.requests.get', return_value=resp):
with pytest.raises(requests.exceptions.HTTPError):
get_latest_image('missing-repo')
def test_get_latest_image_raises_keyerror_on_malformed_body():
resp = make_response(json_data={'digest': 'sha256:abc'})
with patch('deploy_utils.requests.get', return_value=resp):
with pytest.raises(KeyError):
get_latest_image('myapp')
def test_get_latest_image_propagates_network_timeout():
with patch('deploy_utils.requests.get', side_effect=requests.exceptions.Timeout):
with pytest.raises(requests.exceptions.Timeout):
get_latest_image('myapp')
Actually run with pytest, output:
test_deploy_utils.py::test_get_latest_image_returns_tag_on_success PASSED
test_deploy_utils.py::test_get_latest_image_raises_on_http_error PASSED
test_deploy_utils.py::test_get_latest_image_raises_keyerror_on_malformed_body PASSED
test_deploy_utils.py::test_get_latest_image_propagates_network_timeout PASSED
4 passed
Complexity
This is straightforward, constant-time mocked I/O per test, no algorithmic complexity to speak of; the interesting design decision is which failure modes are worth a dedicated test, not runtime cost.
Edge cases
- A 500 server error takes the exact same code path as a 404, since both raise via
raise_for_status(); one test covering "any HTTP error" is representative, a second status-specific test adds little. - A response that's valid JSON but not a dict at all (a bare list, for example) would raise
TypeErrorrather thanKeyErrorwhen['tag']is applied; worth a fifth test if this API's contract is genuinely uncertain. - Real production code often uses a
requests.Sessionwith a configured retry adapter rather than a barerequests.get; mocking at therequests.getlevel, as done here, doesn't exercise that retry behavior at all, which would need a different test approach.
Trade-offs and pitfalls
Mocking at the requests.get level is fast and has zero network flakiness, but it also means these tests can't catch a real integration problem, like the registry's actual response shape changing; a smaller number of separate integration tests against a real or realistic staging registry are worth having alongside these, not instead of them. A common pitfall is mocking so aggressively that the test asserts almost nothing about get_latest_image's own logic, for example forgetting to assert on the exact URL called, which would let a bug in the f-string (a wrong path, a typo) slip through unnoticed.
Security review: you find an IaC change that opens a security group to 0.0.0.0/0 for SSH and stores a service account key in a repo variable file. As the reviewer, propose a remediation plan that enforces least privilege, performs minimal disruption, and includes a migration path to rotate credentials and tighten rules. Include both code changes and rollout steps.
Sample Answer
Direct answer
Treat this as two separate, both-urgent problems: the network rule is wide open (0.0.0.0/0, meaning any IP address on the internet, on port 22, the SSH, or secure shell, port), and there's a live credential sitting in version control where anyone with repository access, including in its full history, can read it. Block the pull request (PR) immediately, revoke the exposed key as a first action independent of the rest of the fix, then land a staged remediation: narrow the network rule, move the credential to a secrets manager, and roll both changes out in an order that doesn't lock out legitimate access.
Structured elaboration
IaC (infrastructure as code) means infrastructure like network rules is defined in version-controlled configuration files rather than clicked together manually.
Immediate actions, before any code review of the fix itself. Revoke or rotate the exposed service-account key right away. Removing a secret from the current version of a file does not remove it from that file's git history, so treat the leak as effectively public the moment it's committed, whether or not it's actually been read by anyone yet.
Code changes for least privilege. Replace the 0.0.0.0/0 SSH rule with either a narrow allow-list of known admin IP ranges, or, stronger, remove inbound SSH entirely and require a bastion host (a single hardened server that's the only machine allowed to SSH into everything else, so you lock down one entry point instead of every server having its own open port) or a managed session broker, such as a cloud provider's session-manager service, that needs no open inbound port at all. Move the service-account key out of the repository variable file and into a managed secrets store, and change the consuming code to read the secret from there via a scoped identity, an instance or task role, rather than a static key baked into config.
Minimal-disruption rollout. Land the network-rule change in a lower environment first and verify admin access still works through the new path before touching production. For the secret migration, run the new secret-store-based access path in parallel with the old key briefly, so nothing breaks mid-cutover, then remove the old key from the variable file and from the running configuration once the new path is confirmed working.
The credential-rotation path specifically. Generate a new key inside the secrets store, deploy the change that reads from the secrets store, verify functionality against the new key, then revoke the old key, in that order. Never revoke the old key first and hope the new path works, that risks an outage instead of a security fix, except for the already-leaked key itself, which is the one case where the security risk of leaving it live outweighs the availability risk, so it gets revoked immediately, even ahead of the rest of the remediation.
Preventing recurrence. Add a CI (continuous integration) check that scans for secrets in commits before merge, and a policy check that rejects an inbound rule allowing 0.0.0.0/0 on an administrative port like 22, so this same class of mistake can't land again without an explicit, reviewed exception.
Worked example
An illustrative before-and-after in Terraform-style HCL (HashiCorp Configuration Language, the syntax Terraform configuration files are written in; the exact resource and argument names vary by IaC tool and cloud provider, so verify these against the actual provider docs the team uses before merging):
# Before: open to the world
resource "example_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# After: narrowed to a known admin range (or remove entirely if using a session broker)
resource "example_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.admin_cidr_allowlist # e.g. the office/VPN egress range, not the whole internet
}
The secret migration follows the same before-and-after shape: the old file held a plaintext value (service_account_key = "...") checked directly into the repository; the new code instead reads a reference to a secrets-store entry, a name or identifier, not the key value itself, and the actual key material lives only inside the secrets store and in the running process's memory, never in a file version control tracks.
Rollout steps, in order. (1) Revoke the exposed key immediately, independent of everything else. (2) Merge and deploy the network-rule narrowing to a non-production environment first, and confirm admin access still works. (3) Generate a new key in the secrets store and deploy the code change that reads from it. (4) Verify the service still functions against the new key. (5) Roll the network and secret changes to production the same way. (6) Add the CI secret-scanning and network-policy checks so this class of issue can't silently recur.
Trade-offs and pitfalls
Narrowing the SSH rule to an office or VPN IP range is simpler to roll out than removing inbound SSH entirely, but it's weaker, since anyone on that network segment still has network-level access; removing inbound SSH in favor of a session broker is the stronger fix but a bigger change that needs its own testing. Rotating a credential before its new access path is verified working risks an availability incident, which is why the rollout order above deploys the new path first and only then revokes the old key, with the sole exception of the already-leaked key, whose security risk outweighs that availability concern.
You open a PR that contains 25 files with mixed issues: a bug in a provisioning script, a security misconfiguration in Terraform, and many minor style issues. As the reviewer, explain how you would triage and classify comments into 'must-fix before merge', 'should-fix before merge', and 'optional', and give two example comments for each category with justification.
Sample Answer
Direct answer
On a 25-file pull request (PR, a proposed code change submitted for review), I triage by actual risk and blast radius, not by diff size: the provisioning-script bug and the Terraform security misconfiguration are must-fix because they change behavior or expose risk, most naming or structure issues are should-fix depending on whether they'll cost real time later, and pure style preferences that a linter could enforce are optional or shouldn't be a manual comment at all.
Structured elaboration
How I classify
- Must-fix before merge: anything factually wrong, or that causes an outage or security exposure, or violates a hard team rule. If I can point to a concrete failure scenario, it's must-fix.
- Should-fix before merge: real quality issues that won't break anything today but will cost real time later (unclear naming, missing error handling on a path likely to be hit, no test for new logic). Negotiable in a specific stated case, but the default is fix it.
- Optional: preference, style, or a nice-to-have that doesn't change correctness or meaningfully affect future maintainability. If a linter or formatter could enforce it, it shouldn't be a manual comment at all.
Two example comments per category
Must-fix
- "The provisioning script writes the instance ID to a temp file before checking whether the previous run's file still exists, so a retried run silently appends stale data instead of failing loudly. This will misconfigure real instances on retry." Justification: a concrete correctness bug with an observable, harmful production effect.
- "This Terraform resource sets the security group's inbound rule to allow all internet addresses on port 22 (SSH, the protocol used for remote server access). This needs to be scoped to the internal network's address range before merge." Justification: an active security exposure, not a style question; shipping it creates real risk the moment it's applied.
Should-fix
- "This function handles three unrelated things: validation, provisioning, and notification. Pulling the notification piece out would make this testable in isolation and easier for the next person to change on its own." Justification: doesn't break anything today, but the coupling will slow down every future change to this function.
- "There's no test covering the retry path this PR adds. Given the must-fix bug above lived exactly in that retry path, a test here would have caught it." Justification: directly tied to the risk just found, not a generic "add more tests" comment.
Optional
- "nit: could use an f-string here instead of string concatenation, purely a style preference, not blocking." Justification: no functional or long-term readability cost either way, and explicitly labeled non-blocking so the author knows they can skip it.
- "nit: consider renaming the loop variable for clarity, but it's also readable as-is from context." Justification: minor and defensible either way, flagged as optional rather than demanded.
Worked example
Given all three issue types in the same PR, I'd leave the two must-fix comments first, clearly marked (a "BLOCKING:" prefix, or the review tool's "request changes" status), and ask for a re-review specifically on those two before anything else. The should-fix comments go in the same review round but don't need a second look; I'd trust the author to either fix them or explicitly push back with a reason. The optional style comments get grouped at the bottom, or left as a single batch of nits, so they don't compete visually with the two things that actually block merge, and I say explicitly that the PR is approvable once the two blocking items are addressed, regardless of whether the nits are touched.
Trade-offs and pitfalls
- Leaving 15 style nits with the same visual weight as the security misconfiguration buries the one comment that actually matters; always lead with, and visually separate, the must-fix items
- Treating "should-fix" as a synonym for "must-fix" just because you feel strongly about it erodes the whole point of the tiering; if it isn't tied to a concrete failure or real future cost, it isn't must-fix
- Style-only nits that a formatter or linter could catch shouldn't be manual review comments at all, that's a signal to add the check to continuous integration (CI, the automated build/test pipeline) instead of repeating the same comment on every PR
You are reviewing a Kubernetes deployment manifest in a PR. The file (excerpt) is:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingest
spec:
replicas: 3
template:
spec:
containers:
- name: ingester
image: mycompany/ingester:latest
List missing operational and security best practices you would require (resource requests/limits, probes, image policies, RBAC, securityContext, labels, etc.) and provide the exact changes you would request in the PR.
Sample Answer
Direct answer
This manifest is missing almost every operational and security safeguard a production deployment needs: no resource requests or limits, no health probes, an unpinned floating image tag, no selector or labels, and no security hardening. Each of these is a specific, concrete change to request in the pull request (PR), not a vague "please harden this."
Structured elaboration
- Resource requests and limits. Without them, one noisy pod can starve its neighbors of CPU (central processing unit) and memory on the same node, or get evicted unpredictably under cluster pressure with no warning.
- Probes. A liveness probe restarts a pod that's stuck; a readiness probe stops traffic from being routed to a pod before it's actually ready to handle it. Without either, Kubernetes has no way to know this container is unhealthy until something downstream notices requests failing.
- Image policy.
image: mycompany/ingester:latestis a floating tag, meaning the same tag can point at different code tomorrow than it does today, so nobody can say with certainty what's actually running in production at any given moment. Pin an immutable version tag instead. - Selector and labels. The spec is missing
spec.selector.matchLabelsentirely, which Kubernetes requires to know which pods this Deployment actually manages. Missing labels also break anything, dashboards, alerting rules, that groups pods by label. - RBAC (role-based access control) and service account. No
serviceAccountNameis set, meaning the pod runs with the namespace's default service account and whatever permissions that happens to have, instead of a minimal, purpose-built identity scoped to what this workload actually needs. - securityContext. Nothing here prevents the container from running as root or gaining new privileges inside the pod.
Worked example
The exact corrected manifest to request in the PR:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingest
labels:
app: ingest
team: data-platform
spec:
replicas: 3
selector:
matchLabels:
app: ingest
template:
metadata:
labels:
app: ingest
team: data-platform
spec:
serviceAccountName: ingest-sa
containers:
- name: ingester
image: mycompany/ingester:v1.2.3
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "1Gi"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
securityContext:
fsGroup: 2000
cpu: "250m" means 250 millicpu, a quarter of one CPU core, a starting point to tune against real observed usage once the service is actually running, not a universal constant to copy everywhere unchanged.
Trade-offs and pitfalls
Setting limits too tight causes the container to be throttled or killed under normal load, so requests and limits should come from observed usage where possible, and get revisited after the service has run for a while, not be treated as set-once-and-forget. readOnlyRootFilesystem: true sometimes breaks an application that writes temp files to disk, if so, mount a small writable emptyDir volume for exactly that path rather than disabling the protection entirely. This level of hardening is a reasonable starting checklist; a real production rollout would also want a PodDisruptionBudget (limits how many pods can be down at once during voluntary disruptions like node maintenance) and, for a service with variable load, a HorizontalPodAutoscaler (automatically adds or removes pod replicas based on load), both reasonable to request as fast follow-ups rather than blocking this PR on.
Unlock Full Question Bank
Get access to all 31 Code Review and Working with Existing Codebases interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.