Apple Site Reliability Engineer (Entry Level) - Comprehensive Interview Preparation Guide
Apple's entry-level SRE interview process consists of 7 total rounds: an initial recruiter screening call, a technical phone screen with the hiring manager covering coding and systems knowledge, followed by a 4-round virtual on-site focusing on systems internals, networking, coding, and system design, with a final manager round for cultural and motivational fit assessment. The process emphasizes depth of technical knowledge, troubleshooting ability, and understanding of SRE principles like reliability, monitoring, and incident response. Interviews are conducted by future teammates and hiring managers, with varying focus areas designed to comprehensively evaluate your readiness for the role.
Interview Rounds
Recruiter Screening
What to Expect
Your initial conversation with Apple's recruiting team to confirm basic fit, discuss the role, and schedule your phone screen with the hiring manager. This is typically a 30-minute call where the recruiter validates your interest in SRE, reviews your background at a high level, and explains the interview process. They may ask about your availability, salary expectations, and visa requirements. Use this time to clarify what the role entails, ask about the team structure, and understand the interview format and timeline.
Tips & Advice
Come prepared with 2-3 specific questions about the SRE role at Apple, the team you'd join, and current challenges they're facing. Be enthusiastic about SRE and reliability engineering. Have your resume and calendar ready. Don't use this round to negotiate salary; focus on moving forward. Be concise and professional.
Focus Topics
Logistics and Availability
Be clear about your availability for interviews, timezone, visa sponsorship needs (if applicable), and expected start date. Confirm you can commit to a multi-week interview process.
Practice Interview
Study Questions
Career Background and Motivation
Prepare a concise overview of your background, relevant experiences (internships, projects, coursework), and why you're interested in joining Apple's SRE team. Highlight any experience with system reliability, automation, monitoring, or incident response.
Practice Interview
Study Questions
Understanding the SRE Role
Articulate what SRE means to you and why you're interested in it. Understand that SRE combines software engineering with operations, focusing on reliability, automation, and reducing toil. Be prepared to explain the difference between SRE and traditional system administration.
Practice Interview
Study Questions
Hiring Manager Phone Screen
What to Expect
This 60-90 minute technical phone screen with your potential hiring manager combines coding, Linux/systems knowledge, and behavioral questions. You'll solve a LeetCode Easy-level coding problem using platforms like CoderPad, answer deep technical questions about Linux internals (file descriptors, inodes, system calls), and discuss your problem-solving approach and background. The hiring manager is assessing your technical depth, how you think through problems, and your communication style. They want to see if you can explain complex concepts clearly and demonstrate foundational systems knowledge expected of an entry-level SRE.
Tips & Advice
Practice LeetCode Easy problems with two-pointer, hash map, and array manipulation techniques. Before the interview, review Linux fundamentals thoroughly—be ready to explain low-level concepts like how 'ls -l' works, what file descriptors are, and how system calls function. Verbalize your thought process when coding and solving problems; silence makes interviewers uncertain. If you don't know something, acknowledge it and explain how you'd approach learning it. Focus on communication and demonstrating foundational understanding rather than memorized facts.
Focus Topics
Behavioral: Problem-Solving Approach and Communication
Demonstrate how you approach unfamiliar problems, ask clarifying questions, break down complexity, and communicate your thinking clearly. Show that you can adapt when stuck and learn incrementally.
Practice Interview
Study Questions
Inter-Process Communication (IPC)
Cover mechanisms for processes to communicate: pipes, sockets, message queues, shared memory, and signals. Know when to use each and basic use cases. Understand how data flows between processes.
Practice Interview
Study Questions
Virtual Address Space and Memory Isolation
Understand how each process has its own virtual address space, how the OS maintains this isolation, and why it matters for security and stability. Know that physical memory is mapped to virtual addresses.
Practice Interview
Study Questions
Linux File System Fundamentals (ls -l, Inodes, File Descriptors)
Understand how 'ls -l' output maps to filesystem data structures. Know what inodes are (metadata containers), file descriptors (integer handles to open files), and how they relate. Be able to explain the difference between a file path and a file descriptor, and how hard links work.
Practice Interview
Study Questions
System Calls (fork, exec, opendir, stat)
Know what each system call does: fork creates a new process, exec replaces the current process image, opendir opens a directory stream, stat retrieves file metadata. Understand their role in process creation, program execution, and file inspection.
Practice Interview
Study Questions
LeetCode Easy - Two Pointers and Array Problems
Master common two-pointer patterns (reverse arrays, palindromes, removing elements), sliding window problems, and basic array manipulation. Problems typically involve finding pairs, removing duplicates, or partitioning arrays. Code should be clean, optimized, and explained step-by-step.
Practice Interview
Study Questions
On-Site: Systems Internals and Linux Troubleshooting Round
What to Expect
This 60-90 minute round focuses on practical Linux troubleshooting and systems knowledge. You'll receive a scenario-based 'Dungeon and Dragons' style troubleshooting challenge (e.g., 'SSH is not working and you have console access—diagnose and fix'). You'll need to think through the problem methodically, navigate the /proc filesystem, understand how shell commands are interpreted, and reason about memory management and kernel interactions. The interviewer will probe your understanding of each step and may ask follow-up behavioral questions about how you approach production issues.
Tips & Advice
Practice with sadservers.com to simulate real Linux troubleshooting scenarios. For the hypothetical scenario, think out loud: start by understanding the scope (what's not working?), then systematically check each layer (network config, services, permissions). Know how to navigate /proc to inspect processes, file descriptors, and system state. Understand shell execution flow: how the shell parses commands, locates executables, and handles I/O redirection. Be methodical rather than random—explain your reasoning at each step. Reference 'Unix and Linux System Administration Handbook' for practical troubleshooting methodology.
Focus Topics
Common Debugging Tools (strace, lsof, ps, top, journalctl)
Know when and how to use each tool: strace for system call tracing, lsof for open files, ps/top for process inspection, journalctl for system logs. Practice running these and interpreting output.
Practice Interview
Study Questions
Shell Command Execution and Interpretation
Understand how the shell parses and executes commands: tokenization, variable expansion, pathname expansion (globbing), redirection, piping. Know the difference between built-in commands and external programs. Understand how stdin/stdout/stderr are connected.
Practice Interview
Study Questions
Behavioral: Handling Ambiguity and Production Pressure
Show how you approach undefined problems with incomplete information. Demonstrate that you ask clarifying questions, make reasonable assumptions, and systematically narrow possibilities. Show calmness under pressure—troubleshooting is often time-sensitive.
Practice Interview
Study Questions
Memory Management and Virtual Memory Concepts
Understand how the kernel manages memory: physical vs. virtual memory, page tables, swapping, memory mapping. Know how to check memory usage (free, /proc/meminfo) and understand swap behavior. Understand why an out-of-memory condition causes system issues.
Practice Interview
Study Questions
Scenario-Based Linux Troubleshooting (SSH, Networking, Services)
Practice methodically diagnosing issues: SSH not working, service won't start, network unreachable. For each, establish a mental model of the system, identify the failure point (network layer? service? permissions?), and verify each assumption. Use standard tools: ping, curl, netstat, ss, systemctl, journalctl.
Practice Interview
Study Questions
/proc Filesystem Navigation and Process Inspection
Master navigating /proc to understand system state: /proc/[pid]/fd for open file descriptors, /proc/[pid]/maps for memory layout, /proc/sys for kernel parameters, /proc/net for network info. Use tools like 'cat /proc/[pid]/status' to inspect processes.
Practice Interview
Study Questions
On-Site: SRE and Networking Fundamentals Round
What to Expect
This 60-90 minute round tests your understanding of networking protocols and SRE concepts. You'll be asked deep-dive questions on TCP, TLS, HTTP, and DNS. A common scenario is tracing the path of a request to a service (e.g., 'What happens when you visit icloud.com?'). You'll need to explain the full stack: DNS resolution, TCP connection establishment (3-way handshake), TLS negotiation, HTTP request/response, and potential failure points. Behavioral questions explore how you'd monitor systems and respond to incidents. The goal is assessing your understanding of the protocols that underpin internet services and how reliability is maintained.
Tips & Advice
Build deep understanding of each protocol layer rather than surface-level knowledge. For TCP, know the 3-way handshake, connection states, and retransmission logic. For TLS, understand the handshake, certificate verification, and cipher negotiation. For HTTP, know methods, status codes, headers, and caching. For DNS, know the resolution process, record types, and caching. Practice drawing a full request path: DNS query → TCP connection → TLS handshake → HTTP request → response → connection teardown. Explain potential failure points at each step. Reference 'Computer Networking: A Top-Down Approach' or similar resources. Understand SRE concepts: SLOs (Service Level Objectives), error budgets, and how monitoring supports reliability.
Focus Topics
DNS Resolution Process and Record Types
Understand recursive and iterative DNS queries, the role of resolvers and authoritative nameservers, and DNS caching. Know common record types (A, AAAA, CNAME, MX, TXT, NS). Understand TTL and how it affects failover speed.
Practice Interview
Study Questions
Behavioral: Incident Response and Problem-Solving
Describe how you'd approach a production incident: gather information, formulate hypotheses, test them systematically, and escalate appropriately. Show you understand the importance of communication and post-incident reviews. Discuss how you'd learn from failures.
Practice Interview
Study Questions
SRE Concepts: SLOs, Error Budgets, and Monitoring
Understand Service Level Objectives (SLOs) and how they define acceptable availability/performance. Know error budgets: if SLO is 99.9% uptime, error budget is 43 minutes/month—once exhausted, focus shifts to stability. Understand the role of monitoring in maintaining SLOs and detecting violations early.
Practice Interview
Study Questions
TCP Protocol and Connection Lifecycle
Master the TCP 3-way handshake (SYN, SYN-ACK, ACK), connection states (LISTEN, ESTABLISHED, TIME_WAIT), and graceful/abnormal connection closure (FIN, RST). Understand sequence numbers, acknowledgments, and sliding window flow control. Know common TCP options and tuning parameters.
Practice Interview
Study Questions
Request Path Tracing (e.g., Accessing a Service Like iCloud.com)
Trace a full request path from client to server: DNS resolution of domain name, TCP connection to IP address, TLS handshake, HTTP request with headers and payload, server processing, response generation, and connection closure. Identify where failures can occur at each step and how to diagnose them.
Practice Interview
Study Questions
TLS/HTTPS Protocol and Certificate Verification
Understand the TLS handshake: ClientHello, ServerHello, certificate exchange, cipher suite negotiation. Know how certificates are verified (chain of trust, expiration). Understand common TLS versions (1.2, 1.3) and what improved in 1.3.
Practice Interview
Study Questions
HTTP Protocol and Request/Response Cycle
Know HTTP methods (GET, POST, PUT, DELETE), status codes (200, 301, 404, 500, 502, 503), and common headers (Content-Type, Authorization, Cache-Control). Understand HTTP keep-alive and connection pooling. Know the difference between HTTP/1.1, HTTP/2, and HTTP/3.
Practice Interview
Study Questions
On-Site: Coding and Algorithms Round
What to Expect
This 60-75 minute round tests algorithmic problem-solving with LeetCode Easy-Medium problems, often involving graph traversal (BFS/DFS). You'll code on a shared platform or whiteboard and must articulate your approach, optimize your solution, and discuss trade-offs. The interviewer will observe how you think through complexity, ask clarifying questions, handle edge cases, and improve your solution. For entry-level, demonstrating solid fundamentals and clear thinking is more important than perfect optimization.
Tips & Advice
Practice LeetCode Easy-Medium problems focusing on BFS/DFS, arrays, and linked lists. Start with clarifying the problem: constraints, edge cases, expected input/output. Verbalize your approach before coding. Code cleanly with meaningful variable names. Test your solution mentally with examples. If stuck, explain your thinking rather than falling silent. Optimize after getting a working solution. Time management is critical—finish the first solution and optimize only if time allows. For entry-level, a working solution that handles edge cases is a strong outcome.
Focus Topics
Code Quality and Edge Case Handling
Write clean, readable code with meaningful names. Handle edge cases: empty inputs, single elements, negative numbers, duplicates. Test your solution mentally before declaring it complete. Consider off-by-one errors.
Practice Interview
Study Questions
Data Structures: Arrays, Linked Lists, and Hashmaps
Master common operations on arrays (indexing, slicing, sorting), linked lists (insertion, deletion, reversal), and hashmaps (insertion, lookup, collision handling). Understand when each is appropriate and their complexity trade-offs.
Practice Interview
Study Questions
Problem-Solving Strategy and Communication
Develop a consistent approach: understand the problem, identify constraints, think of examples, design algorithm, code, test with examples. Communicate each step. Ask for clarification. Discuss trade-offs. Show you're thinking, not just coding.
Practice Interview
Study Questions
BFS (Breadth-First Search) and Graph Traversal
Master BFS implementation using queues, level-order traversal, shortest path problems. Understand when BFS is appropriate (unweighted shortest path, level exploration). Know complexity (O(V+E)) and space requirements.
Practice Interview
Study Questions
DFS (Depth-First Search) and Recursive Problem Solving
Master DFS using recursion and stacks, backtracking for permutations/combinations, and path-finding. Understand when DFS is useful (topological sort, cycle detection). Know complexity and how to reason about recursion depth.
Practice Interview
Study Questions
On-Site: System Design and Architecture Round
What to Expect
This 60-75 minute round assesses your ability to design scalable, reliable systems. For entry-level, expect simpler design problems (e.g., design a file sharing service or monitoring system) rather than complex infrastructure. You'll need to discuss trade-offs in architecture, including load balancing strategies, database choices, caching, and how to ensure reliability and observability. The focus is on your thought process: how you identify requirements, make design decisions, and consider operational concerns like monitoring and incident response. This round often includes behavioral questions about how you'd work with teams to implement and maintain such a system.
Tips & Advice
For entry-level system design, focus on fundamentals rather than advanced patterns. Start by clarifying requirements and constraints. Draw simple diagrams showing components and interactions. Discuss scalability (horizontal scaling with load balancing), reliability (replication, failover), and observability (logging, monitoring, alerting). Consider operational aspects: how would you deploy, monitor, and debug this system? Know basic concepts like load balancing strategies (round-robin, least-connections), database replication, caching strategies, and API design. Reference 'Designing Data-Intensive Applications' for foundational concepts, but don't overcomplicate. For entry-level, a well-reasoned, well-communicated design is strong—you're not expected to cover every edge case.
Focus Topics
Behavioral: Cross-Functional Collaboration
Discuss how you'd work with frontend teams, backend engineers, and other SREs to build and operate the system. Show you understand different perspectives (performance vs. reliability vs. feature velocity). Demonstrate communication and problem-solving skills.
Practice Interview
Study Questions
API Design and Communication Patterns
Discuss REST API design, error handling, versioning, and rate limiting. Understand synchronous vs. asynchronous communication (queues, event streams). Consider how to design APIs that are easy to integrate and maintain.
Practice Interview
Study Questions
Database and Data Storage Decisions
Understand trade-offs between relational databases (ACID, structured schema), NoSQL (scalability, flexibility), and caching layers (performance). Discuss when to use each. Know basics of replication and sharding for scaling databases.
Practice Interview
Study Questions
Scalable System Architecture and Load Balancing
Understand how systems scale: horizontal scaling (multiple servers), statelessness, and load balancing strategies (round-robin, least-connections, IP hash). Know the role of load balancers in distributing traffic and handling failures. Discuss trade-offs between simple and complex strategies.
Practice Interview
Study Questions
Reliability and Fault Tolerance Principles
Understand replication (data consistency across copies), failover mechanisms, and redundancy. Know concepts like eventual consistency, strong consistency, and CAP theorem basics. Discuss how to ensure service continues if individual components fail.
Practice Interview
Study Questions
Monitoring, Observability, and Alerting
Discuss how to observe system health: metrics (request latency, error rates), logs (structured logging), and traces (request flow). Explain what to alert on (SLO violations, errors) vs. what to dashboard. Understand the role of observability in incident response.
Practice Interview
Study Questions
On-Site: Manager and Cultural Fit Round
What to Expect
This 45-60 minute round is typically with your hiring manager or a senior manager and focuses on behavioral, motivational, and cultural alignment. You'll discuss your career goals, why you're passionate about SRE, how you handle challenges, your learning style, and how you work in teams. The manager assesses whether you'll be a good fit for the team and organization, your growth potential, and your ability to collaborate. This round may also include discussion of how you'd approach production incidents, learning from failures, and contributing to team culture. It's less technical and more about understanding you as a person and professional.
Tips & Advice
Be authentic and thoughtful in your responses. Prepare specific examples of challenges you've overcome, lessons learned, and how you've grown. Show genuine curiosity about the role and team. Discuss what excites you about SRE and why you want to join Apple. Ask thoughtful questions about team culture, technical challenges, and growth opportunities. Listen carefully and connect your answers to the team's needs. Avoid generic answers; managers can tell when you're not being genuine. Prepare 2-3 questions that show you've researched Apple and the role. Focus on demonstrating learning ability, resilience, and collaborative mindset—critical for entry-level success.
Focus Topics
Questions About Team, Role, and Growth
Prepare 3-5 thoughtful questions: What does the team currently focus on? What are technical challenges? How does the team approach reliability? What growth opportunities exist? These questions show genuine interest and help you assess fit.
Practice Interview
Study Questions
Handling Failure and Incident Learning
Describe a significant mistake or failure you've experienced and what you learned from it. Show you don't make excuses but take responsibility and extract lessons. Discuss how you'd approach post-incident reviews constructively. Demonstrate psychological safety and blameless culture understanding.
Practice Interview
Study Questions
Production Awareness and Incident Response Mentality
Discuss your understanding of production environments and the importance of reliability. Talk about how you'd approach production incidents: prioritizing user impact, clear communication, and systematic diagnosis. Show you understand 'on-call' responsibility.
Practice Interview
Study Questions
Motivation for SRE and Role Understanding
Articulate why you're passionate about site reliability engineering specifically. Discuss what appeals to you: reliability, automation, incident response, or infrastructure. Show you understand the role's blend of engineering and operations. Be specific, not generic.
Practice Interview
Study Questions
Learning Ability and Growth Mindset
Describe how you learn new technologies and concepts. Give examples of challenges where you lacked initial knowledge but learned and succeeded. Show curiosity and willingness to invest in growth. Demonstrate you seek feedback and incorporate it.
Practice Interview
Study Questions
Teamwork and Collaboration
Discuss how you work in teams, handle disagreements, and contribute to group goals. Give examples of collaborating with colleagues from different backgrounds or disciplines. Show you value diverse perspectives and can adapt your communication style.
Practice Interview
Study Questions
Frequently Asked Site Reliability Engineer (SRE) Interview Questions
In your experience, what separates someone who genuinely has a growth mindset from someone who just talks about learning? What have you seen actually change how a team works, and what quietly undermines it?
Sample Answer
Direct answer
The tell is behavior you can point to weeks later, not activity. Someone with a genuine growth mindset can name one specific thing they do differently now because of what they learned. Someone who's performing it can list courses and buzzwords, but their day-to-day decisions look identical to before.
Structured elaboration
Performative signals I watch for: talking about "failing fast" but never actually changing an approach after a failure; a training budget spent with no artifact, decision, or habit traceable to it; and growth-mindset language used defensively, in the exact moment someone is avoiding a hard truth about their own work.
Team practices I've seen actually make it real:
- Postmortem write-ups that stay focused on the system or process gap, not a person, so admitting "I didn't know that" in a meeting is normal rather than costly.
- Giving explicit credit for surfacing a problem early, even to the person who caused it, since that's the behavior you want more of.
Anti-patterns that quietly kill it:
- Punishing the person who admits the mistake, even subtly, in tone or in a later performance conversation.
- Rewarding only polished outcomes and never visible struggle.
- A leader who says "bring me problems" but visibly bristles when someone actually does.
Signals I'd look for when hiring or promoting: ask for a specific instance of being wrong and what changed, not a definition of growth mindset. How someone talks about a mistake more than a year old is revealing: genuine change tends to be stated plainly and briefly, performative change tends to be over-explained or moralized. Underneath all of this, psychological safety, meaning people trust that admitting a gap won't be held against them, is a precondition rather than a nice-to-have: people only admit gaps where admitting them is safe, so a team that talks about growth but visibly punishes visible failure will get performative language and fixed-mindset behavior, because people respond to the real incentive, not the stated value.
Worked example
A team I was on instituted blameless postmortems after a string of on-call incidents where the write-ups had previously named individuals. In the months after, two things actually changed: people started flagging near-misses in planning meetings before they became incidents, which they hadn't done before, and the same root cause stopped repeating quarter over quarter because fixes addressed the system gap instead of reminding one person to be careful. That's the distinction I use: the postmortem template changing was the activity, people voluntarily surfacing risk earlier was the evidence.
Trade-offs and pitfalls
The common failure mode in this answer is describing an organization's stated values instead of an observed behavior change, or confusing enthusiasm (showing up to every workshop) with capability. A team-level answer with zero self-implication also reads as performative itself: a credible answer names an anti-pattern the candidate has personally been guilty of, not just ones they've witnessed in others.
A script starts several background tasks and finishes with exit status 0 even though one of them failed. Explain why this happens, and write a pattern that waits for every task, records each outcome, and makes the script fail if any task failed.
Sample Answer
The script exits 0 because a bare wait (with no arguments) waits for all background jobs and then returns 0 no matter how they ended, and the script's own exit status is the status of its last command. A background job's failure is invisible unless you collect it. The fix is to remember each job's process ID (PID), call wait PID for each one separately (that form returns that job's exit status), store the result, and end the script with a status computed from the results.
Why it happens
- Starting a command with
&runs it in a background subprocess. The shell does not stop or fail when it later exits non-zero:set -eonly watches foreground commands. waitwith no operands returns 0 once every child has finished.wait PIDreturns the exit status of that process; if it was killed by a signal, the status is 128 plus the signal number (143 for SIGTERM, 137 for SIGKILL).- The shell keeps the exit status of a finished background job until you ask for it, so waiting for a job that already finished still returns its real status.
$!holds the PID of the most recent background job, so capture it immediately after the&.
The pattern
- Start every task and record
$!against a name. - Loop over the names,
waitfor each PID, and store the status. Do not useset -ein this loop (or writewait "$pid" || rc=$?), or the first failure aborts before the others are collected. - Report every outcome, count the failures and exit non-zero if there were any.
Demonstration (Ubuntu 24.04, bash 5.2)
The broken version: three tasks, the second exits with status 3.
#!/usr/bin/env bash
set -euo pipefail
task() { sleep "$2"; echo "task $1 done"; return "$3"; }
task a 0.1 0 &
task b 0.2 3 & # fails after the others start
task c 0.3 0 &
wait # no operands: waits for all, returns 0 whatever they did
echo "reached the end, wait returned $?"
The fixed version: four tasks with different delays and exit codes (b returns 3, d returns 7), results stored in associative arrays. An associative array (declare -A) is a variable that maps text keys to values, like a dictionary: pid_of[a] holds the PID of task a, status_of[b] holds b's exit status. In "${!names[@]}" the ! asks for the array's indices instead of its values (0 1 2 3 for the plain array names), so names[i], delays[i] and codes[i] line up. pid_of[${names[i]}]=$! stores the just-started job's PID under its task name.
#!/usr/bin/env bash
set -uo pipefail
task() { sleep "$2"; echo "task $1 done"; return "$3"; }
declare -A pid_of status_of # name -> pid, name -> exit status
names=(a b c d)
delays=(0.1 0.2 0.4 0.3)
codes=(0 3 0 7)
for i in "${!names[@]}"; do
task "${names[i]}" "${delays[i]}" "${codes[i]}" &
pid_of[${names[i]}]=$!
done
failed=0
for name in "${names[@]}"; do
wait "${pid_of[$name]}" # waits for this pid and returns ITS status
status_of[$name]=$?
if ((status_of[$name] != 0)); then failed=$((failed + 1)); fi
done
for name in "${names[@]}"; do
printf '%s: exit=%d\n' "$name" "${status_of[$name]}"
done
echo "failed tasks: $failed of ${#names[@]}"
((failed == 0)) # script's own exit status
The last line, ((failed == 0)), is an arithmetic test whose result becomes the script's exit status, because it is the last command: it succeeds (status 0) when failed is 0 and returns status 1 otherwise. That is why the run below ends with script rc=1. A fail-fast variant uses wait -n (wait for the next job to finish, bash 4.3 and later) and stops the rest on the first failure. In it, jobs -p prints the PIDs of the jobs still running, and mapfile -t rest < <(jobs -p) reads those lines into an array named rest so kill "${rest[@]}" can stop them all:
#!/usr/bin/env bash
t() { sleep "$1"; return "$2"; }
t 0.3 0 & t 0.1 5 & t 0.5 0 &
for _ in 1 2 3; do
wait -n; rc=$?
if ((rc != 0)); then
echo "first failure: status $rc, stopping the rest"
mapfile -t rest < <(jobs -p)
kill "${rest[@]}" 2>/dev/null
wait 2>/dev/null
exit "$rc"
fi
done
Driver:
exec 2>&1
cd /w
echo "--- broken"; bash broken.sh; echo "script rc=$?"
echo "--- fixed"; bash fixed.sh; echo "script rc=$?"
echo "--- failfast"; bash failfast.sh; echo "script rc=$?"
Output:
--- broken
task a done
task b done
task c done
reached the end, wait returned 0
script rc=0
--- fixed
task a done
task b done
task d done
task c done
a: exit=0
b: exit=3
c: exit=0
d: exit=7
failed tasks: 2 of 4
script rc=1
--- failfast
first failure: status 5, stopping the rest
script rc=5
- Broken: all three tasks finish,
waitreturns 0 and the script exits 0 although taskbfailed. - Fixed: every outcome is recorded (
bis 3,dis 7), the count of failures is 2 of 4, and the script exits 1.dfinished beforec, yet results are reported in launch order because the loop waits per PID. - Fail-fast:
wait -nreturned the status of the first job to end, here the 0.1-second task with status 5. The script stopped the remaining two and exited with that status.
I ran ShellCheck on all three scripts with the koalaman/shellcheck:stable image and it reported nothing.
Choosing between them
| Need | Use |
|---|---|
| Run all, report all, fail at the end | wait PID per task, as in fixed.sh |
| Abort as soon as one fails | wait -n in a loop, then kill the rest |
| Limit concurrency (for example 4 at a time) | start up to N jobs, call wait -n to free a slot before starting the next, and still record statuses |
Identify which job wait -n returned | wait -n -p var (bash 5.1 and later) stores the PID in var |
Pitfalls
waitonly knows jobs of the current shell. A job started inside a pipeline segment or a subshell( ... ) &is a grandchild: its failures are visible only through the subshell's own exit status.- If the script has
set -e, a barewait "$pid"with a non-zero result exits the script immediately, skipping later waits. Usewait "$pid" || status=$?or turn-eoff around the loop. cmd | tee log &reports the status of the last pipeline element (tee) unlessset -o pipefailis on.- Capture
$!right after the&; any other background start changes it. - Do not use
$?after a plainwaitto infer anything: it says only that the wait itself worked. - For many short tasks,
xargs -Por GNUparallelhandle concurrency and exit-status aggregation for you; the pattern above is for a small number of named jobs.
Describe the TCP three-way handshake in detail: which flags are set in each packet (SYN, SYN-ACK, ACK), how sequence and acknowledgment numbers are used, and what state each endpoint moves into after each step. Explain what problem the handshake actually solves.
Sample Answer
Direct answer
The TCP three-way handshake establishes a reliable connection before any data flows: the client sends a SYN, the server replies with a combined SYN-ACK, and the client finishes with an ACK. Its job is to let both sides agree on starting sequence numbers and confirm that both directions of the path actually work before committing application data to the wire.
Structured elaboration
- SYN: the client picks an initial sequence number (ISN, essentially a large pseudo-random 32-bit number) and sends a segment with the SYN flag set and that sequence number. The client moves to the
SYN-SENTstate. - SYN-ACK: the server, if it's listening on that port, picks its OWN initial sequence number, and replies with a segment that has both the SYN flag set (announcing the server's own sequence number) AND the ACK flag set (acknowledging the client's sequence number + 1). The server moves to the
SYN-RECEIVEDstate. - ACK: the client acknowledges the server's sequence number + 1 with a plain ACK segment. Both sides now move to
ESTABLISHED, and either side may now send data.
Why three steps rather than two: TCP needs BOTH sides' sequence numbers acknowledged, since TCP is full-duplex (both directions need independent sequence tracking). A two-way handshake could confirm only one direction; the third message is what confirms the client's original SYN actually arrived, closing the loop for the client's own sequence space.
Worked example
Suppose a client opens a TCP connection to a web server on port 443. The client sends SYN, seq=1000. The server responds SYN, ACK, seq=5000, ack=1001 (acknowledging the client's SYN by number+1). The client responds ACK, seq=1001, ack=5001. From this point, the client's next data byte will carry sequence number 1001, and the server's next data byte will carry sequence number 5001; each side tracks the OTHER side's sequence space independently via the ACK field of every following segment.
Trade-offs & pitfalls
A frequent mistake is describing the handshake as three round trips; it's actually one and a half round trips of latency, because the SYN-ACK piggybacks the server's SYN onto its ACK of the client's SYN. This is also exactly why TCP always incurs at least one round trip of setup latency before any data can flow, which is the whole motivation behind newer mechanisms like TCP Fast Open that try to send data alongside the very first SYN.
You need to roll out an update to a service behind an L7 load balancer that still uses sticky sessions. Compare blue-green, canary, and rolling-update approaches: for each, explain how you would drain connections, how you would migrate or preserve session state, and how you would validate success before committing.
Sample Answer
Direct Answer
All three deployment strategies need the same underlying primitive when sticky sessions are involved: keep serving already-affinitized sessions from their old destination while steering new sessions to the new version, and validate with real traffic before fully committing. What differs is blast radius and rollback speed. Canary gives the smallest blast radius and fastest safe rollback since only a slice of traffic ever touches the new version. Blue-green gives the fastest full rollback, a single traffic flip back to the untouched old fleet. Rolling update is the middle ground: it uses less infrastructure than blue-green but exposes both versions to production traffic for the longest window.
Comparing the Three Approaches
| Approach | Connection draining | Session state handling | Validation before commit | Rollback |
|---|---|---|---|---|
| Blue-green | Old (blue) fleet stays fully up and serving until cutover; drain blue only after traffic is flipped | Best served by an externalized session store so sessions survive the fleet swap cleanly; if sessions are cookie-pinned to instances, the cutover needs a session-mirroring step | Gradual traffic shift (e.g., 10% then 100%) while watching error rate, latency, and session-continuity checks before fully committing | Flip the router back to blue; near-instant since blue was never torn down |
| Canary | New (canary) instances take only new sessions; canary is scaled down and drained gracefully if promoted or rejected | Same principle: externalized state lets a session move between canary and baseline transparently; if using cookie affinity, route by cookie so a canaried session stays on a canary instance able to handle it | Small, tight-SLO evaluation window on a small slice of real traffic, with automated gates (error rate, latency, business metrics) before expanding | Set canary traffic weight to zero and terminate; blast radius was already small, so rollback impact is minimal |
| Rolling update | Each instance is marked out of rotation, allowed to finish in-flight work (or hand off state), then replaced, in small batches | Requires session state to be externalized or explicitly handed off before an instance is replaced, since there's no single "old fleet" to fall back to | Monitor per-batch metrics with a pause-and-check gate between batches, rather than one global before/after comparison | Halt the rollout and redeploy the previous version to the already-replaced instances; slower than blue-green because it's also incremental |
Worked Example
If a canary receives 5% of instance capacity and that version has a latent bug, the fraction of live traffic that can be affected during the validation window is bounded above by that same 5%, by construction, since the routing weight is what determines exposure. A rolling update, by contrast, exposes a growing share of the fleet as each batch completes; if you roll in 10 batches of 10% each and something is only caught at the fourth batch, roughly 40% of the fleet has already been exposed by the time you halt, an order of magnitude more exposure than the canary case for the same underlying bug. This is the concrete reason canary is preferred for higher-risk changes even though it takes more total upfront tooling to run the automated gates.
Trade-offs and Pitfalls
- Blue-green doubles infrastructure cost for the duration of the cutover window, since two full fleets run simultaneously; this is the price of the fastest possible full rollback.
- Rolling update's extended window with two versions live simultaneously creates a real risk of version-skew bugs: if a sticky client reconnects mid-rollout and lands on a different version than its previous request, and the two versions disagree on session schema or API contract, that's a bug class that blue-green and canary largely avoid by keeping version boundaries sharper.
- Canary's small sample size is a double-edged sword: it bounds blast radius but also means a rare bug (one that only manifests on 1 in 1000 requests) may not surface at all before the canary is judged "clean" and promoted.
- Whichever strategy is used, connection draining timeouts need to be sized for the actual protocol involved; a WebSocket-heavy service needs a much longer drain window than a typical request-response API, and a drain window that's too short converts a graceful strategy into a de facto hard cut for anything still in flight.
A production service hangs. Thread dumps show many threads blocked on locks. How do you confirm a deadlock versus a livelock or priority inversion, what do you collect, and how do you recover and prevent a repeat?
Sample Answer
Direct answer
Take thread dumps first (three, about ten seconds apart, before restarting anything) and read the state and the CPU of each blocked thread. A deadlock is a cycle: every thread in it waits for a lock another thread in it holds, the threads use almost no CPU and the stacks are identical in every dump. A livelock is threads that keep running (high CPU) and keep changing state, each retrying or backing off in response to the others, so the stacks differ between dumps and no work completes. Priority inversion is a high-priority thread blocked on a lock held by a low-priority thread that cannot run because medium-priority threads are using the CPU; it exists only under strict-priority scheduling (the scheduler always runs the highest-priority runnable thread) and ends when the medium work finishes. Collect dumps, per-thread CPU and a core file; recover by draining (taking the instance out of load-balancer rotation so no new requests arrive) and restarting the instance (a lock cycle among monitors, the per-object locks the Java virtual machine uses for synchronized, cannot be broken from outside); prevent a repeat with a global lock order, timeouts, and a lock-order check in CI.
Ordered checks
- Confirm the hang is real and scope it. Is any request counter still moving? Does only one instance hang? Take the instance out of the load balancer rotation (drain) before touching it, so users stop hitting it.
- Dump three times. For a Java virtual machine (JVM):
jstack PID > dump1.txt(and again after 10 s and 20 s). For native code:gdb -p PID -batch -ex "thread apply all bt"(not run in the demos here: gdb was not available in the container image used) orgcore PIDto keep a core file (a snapshot of the process's memory and thread state written to disk, which you can inspect after the process is gone) for offline analysis. Capture per-thread CPU:top -H -p PIDorps -L -o tid,pcpu,stat,wchan -p PID(one line per thread:pcpuis its CPU use,statits state, andwchanthe kernel function it is sleeping in, which shows a futex wait, the kernel-assisted sleep on a lock, when it is blocked on a lock). - Classify with the table. A wait-for graph has one node per thread and an arrow from each thread to the thread whose lock it is waiting for; a cycle in that graph is a deadlock.
| Observation | Deadlock | Livelock | Priority inversion | Slow lock holder (not a hang) |
|---|---|---|---|---|
| CPU | near zero | high | low for the blocked, high for medium threads | the holder is busy or waiting on I/O |
| Thread states | all blocked on locks | running or retrying | blocked high thread, runnable-but-not-running low thread | many waiters, one running holder |
| Stacks between dumps | identical | change | identical for the blocked | holder's stack changes |
| Wait-for graph | has a cycle | no stable cycle | no cycle (holder is not waiting for a lock) | no cycle |
| Ends by itself | never | rarely | yes, when medium work stops | yes |
- Find the cycle. The JDK prints it for you. Here is a program with the classic opposite-order locks:
public class Transfer {
static final Object ACCOUNT_A = new Object(), ACCOUNT_B = new Object();
static void move(String name, Object first, Object second) {
synchronized (first) {
pause(200); // widen the window so the demo is deterministic
synchronized (second) { System.out.println(name + " done"); }
}
}
static void pause(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { } }
public static void main(String[] args) {
new Thread(() -> move("a-to-b", ACCOUNT_A, ACCOUNT_B), "worker-1").start();
new Thread(() -> move("b-to-a", ACCOUNT_B, ACCOUNT_A), "worker-2").start();
}
}
Built with javac Transfer.java and run with java Transfer in a Linux container (JDK 21), then checked after 5 seconds with ps -o pid,pcpu,stat,comm, the process showed well under 1 percent CPU and state Sl (sleeping, multithreaded). jstack PID ended with:
Found one Java-level deadlock:
=============================
"worker-1":
waiting to lock monitor 0x0000ffff40001d30 (object 0x00000007152139d0, a java.lang.Object),
which is held by "worker-2"
"worker-2":
waiting to lock monitor 0x0000ffff44000f00 (object 0x00000007152139c0, a java.lang.Object),
which is held by "worker-1"
...
Found 1 deadlock.
(addresses differ per run; the per-thread stack sections below the cycle list the lines waiting to lock and locked for each thread.) Read each block as an arrow: worker-1 waits for a lock held by worker-2, and worker-2 waits for one held by worker-1. That is the cycle. In the ps check, near-zero CPU says nothing is running, and Sl says the process is sleeping (S) and multithreaded (l), the signature of threads that are all blocked. For native code the same information comes from the backtraces: threads parked in pthread_mutex_lock or a futex wait, and the owning thread of each mutex (not run here).
- Priority inversion check. Is a high-priority thread blocked while a lower-priority thread holds the lock and is runnable but not running? Under real-time priorities that is the signature. The fix is a mutex with a priority-inheritance protocol: POSIX
PTHREAD_PRIO_INHERITmeans "when the calling thread is blocked because the mutex is owned by another thread, that owner thread shall inherit the priority level of the calling thread as long as it continues to own the mutex". - Livelock check. Compare two dumps: identical retry loops, rising CPU and flat progress counters. The usual cause is symmetric retry with no randomness (both back off by the same amount).
- A long-running holder. If one thread holds the lock and is calling a remote service or waiting on a disk, it is not a deadlock: look at that holder's stack, not at the waiters.
Recover
A deadlock among JVM monitors cannot be broken without a restart: you cannot safely kill one thread. Order of actions: drain the instance, save the dumps and (for native code) a core file, restart, and shift traffic back. Two mitigations that need no restart for a service that cannot be stopped: drain traffic away from the stuck instance so the fleet keeps serving, and switch off with a feature flag (or route around) the single code path that takes both locks until the fix is deployed. Lock timeouts help only if they already exist in the code: ReentrantLock.tryLock(timeout) in Java, pthread_mutex_timedlock in POSIX.
For processes that deadlock across file locks (a second tool for a different failure; the thread dump and lock order above are the main path): POSIX fcntl locks taken with F_SETLKW are checked by the kernel and one process gets EDEADLK. In this program two processes lock two files in opposite order (compiled with gcc 14.4.0 and run in a Linux container):
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static int lock_wait(int fd) {
struct flock fl = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 1 };
return fcntl(fd, F_SETLKW, &fl); // blocks until granted, or fails with EDEADLK
}
static void worker(const char* name, const char* first, const char* second) {
int f1 = open(first, O_RDWR | O_CREAT, 0600), f2 = open(second, O_RDWR | O_CREAT, 0600);
lock_wait(f1);
usleep(300000);
if (lock_wait(f2) == -1) printf("%s: second lock failed: %s\n", name, strerror(errno));
else printf("%s: got both locks\n", name);
fflush(stdout);
}
int main(void) {
pid_t p1 = fork();
if (p1 == 0) { worker("proc-1", "lockA", "lockB"); return 0; }
pid_t p2 = fork();
if (p2 == 0) { worker("proc-2", "lockB", "lockA"); return 0; }
waitpid(p1, 0, 0); waitpid(p2, 0, 0);
return 0;
}
In the program, F_WRLCK asks for an exclusive write lock, l_whence, l_start and l_len say "one byte at the start of the file", and F_SETLKW means set the lock and Wait if it is held. It printed:
proc-2: second lock failed: Resource deadlock avoided
proc-1: got both locks
Which process loses varies from run to run (either proc-1 or proc-2 can be the one refused, so the two lines above can appear with the names swapped): the one whose second request would close the wait cycle is refused, and the other then gets both locks. EDEADLK is the error number the kernel returns when granting the lock would complete a wait cycle. The man page for F_SETLKW says the error means "It was detected that the specified F_SETLKW operation would cause a deadlock" and that the kernel limits its dependency search to 10 steps, so longer cycles are not detected. To inspect held locks on a live system, /proc/locks and lslocks list them (not run here).
Prevent a repeat
- One global lock order. Always lock the lower account id first. The fixed program ran two threads doing 1,000,000 opposite-direction transfers each, with a 30-second timeout on each result, so a deadlock would have shown as a failure:
import java.util.concurrent.*;
public class Ordered {
record Account(int id, Object lock) { Account(int id) { this(id, new Object()); } }
static void move(Account from, Account to) {
Account first = from.id() < to.id() ? from : to; // one global order: lowest account id first
Account second = first == from ? to : from;
synchronized (first.lock()) {
synchronized (second.lock()) { /* debit from, credit to */ }
}
}
public static void main(String[] args) throws Exception {
Account a = new Account(1), b = new Account(2);
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<?> f1 = pool.submit(() -> { for (int i = 0; i < 1_000_000; i++) move(a, b); });
Future<?> f2 = pool.submit(() -> { for (int i = 0; i < 1_000_000; i++) move(b, a); });
f1.get(30, TimeUnit.SECONDS); // a deadlock would surface as a TimeoutException
f2.get(30, TimeUnit.SECONDS);
pool.shutdown();
System.out.println("2 x 1,000,000 opposite-direction transfers finished, no deadlock");
}
}
javac Ordered.java && java Ordered printed: 2 x 1,000,000 opposite-direction transfers finished, no deadlock.
- Detect order violations without waiting for a hang. ThreadSanitizer (a tool that reports data races and lock misuse at run time) finds lock-order inversions even in a run that did not deadlock. This program takes the two locks in opposite orders one after the other, so nothing can hang:
#include <mutex>
#include <thread>
std::mutex account_a, account_b;
void a_then_b() { std::lock_guard<std::mutex> x(account_a); std::lock_guard<std::mutex> y(account_b); }
void b_then_a() { std::lock_guard<std::mutex> x(account_b); std::lock_guard<std::mutex> y(account_a); }
int main() {
a_then_b(); // run the two paths one after the other: no deadlock can happen in this run
std::thread t(b_then_a);
t.join();
}
g++ -std=c++20 -O1 -g -fsanitize=thread lockorder.cpp -o lo && ./lo printed WARNING: ThreadSanitizer: lock-order-inversion (potential deadlock) with Cycle in lock order graph: M0 => M1 => M0 and the two stacks that took the locks in opposite order. Run the test suite under TSan in continuous integration (CI). Where TSan is not available, record the order locks are acquired in a debug build and fail on a violation.
3. Never hold a lock across a call to unknown code (callbacks, remote calls); that is how long, unexplained cycles appear.
4. Timeouts with backoff on try-lock where a lock order cannot be imposed; add randomness so symmetric retries do not livelock.
5. Alert on a stuck-thread watchdog (no progress counter movement for N seconds) so the next hang is a page with dumps attached, not a customer report.
Verify the fix
Reproduce the original hang with the old code in a test (as in the first program), confirm the new code survives the same harness with a timeout, and keep the TSan job in CI.
Implement an iterative DFS in Python to check whether there exists a path between two nodes in a directed graph. Use early exit to improve performance, handle missing nodes gracefully, and discuss stack size and worst-case complexity.
Sample Answer
Direct answer
Use an explicit stack and a visited set; check whether the popped node is the target BEFORE checking or updating visited state, so the function can return True the instant the target is discovered rather than waiting to fully process it. This early exit is the main performance lever over a full traversal, since for many queries the target is found long before the rest of the graph would have been explored.
Structured elaboration
Path existence between two nodes only needs a yes/no answer, so nothing about a full traversal's bookkeeping (discovery order, per-node results) is required. The traversal can stop the instant the target is popped from the stack, rather than finishing whatever level or subtree it happens to be in. A visited set still guards against revisiting nodes on graphs with cycles, exactly as in any other DFS, but the check for "is this the target" happens first, ahead of the visited check, so that even a target reachable through a cycle is caught immediately rather than only after normal traversal bookkeeping would have gotten to it.
Worked example
def has_path_iterative_dfs(graph, start, target):
if start not in graph and start != target:
return False
if start == target:
return True
visited = set()
stack = [start]
while stack:
node = stack.pop()
if node == target:
return True
if node in visited:
continue
visited.add(node)
for nbr in graph.get(node, []):
if nbr not in visited:
stack.append(nbr)
return False
if __name__ == "__main__":
graph = {
"A": ["B", "C"],
"B": ["D"],
"C": ["D"],
"D": ["A"], # cycle back to A
"E": ["F"], # separate component, unreachable from A
"F": [],
}
print("A -> D reachable:", has_path_iterative_dfs(graph, "A", "D"))
print("A -> E reachable (different component):", has_path_iterative_dfs(graph, "A", "E"))
print("A -> A trivial (same node):", has_path_iterative_dfs(graph, "A", "A"))
print("Z -> A, Z missing from graph:", has_path_iterative_dfs(graph, "Z", "A"))
# Early-exit proof: on a long chain, reaching the immediate neighbor of start
# should not require expanding the rest of a huge unrelated subgraph.
chain = {i: [i + 1] for i in range(100000)}
print("Long chain of 100000 nodes, 0 -> 1 found:", has_path_iterative_dfs(chain, 0, 1))
Output (actually executed with python3):
A -> D reachable: True
A -> E reachable (different component): False
A -> A trivial (same node): True
Z -> A, Z missing from graph: False
Long chain of 100000 nodes, 0 -> 1 found: True
Complexity
- Worst case (target unreachable, or reachable only after exploring most of the graph): time O(V+E), each node and edge examined at most once.
- Best case (target adjacent to
startor found early): far less than a full traversal, the early exit on thenode == targetcheck returns as soon as the target is popped. - Space: O(V) for the visited set; stack size in the worst case also O(V), bounded by the longest unexplored path currently pending.
Edge cases
start == target: handled explicitly up front, returnsTrueimmediately without touching the graph at all.startmissing from the graph entirely (not even a value somewhere): returnsFalseimmediately rather than attempting a lookup that would find nothing anyway.- Empty graph: no nodes to explore, returns
Falseunlessstart == targetwas already caught above. - Very long chains: the early-exit test above uses a 100,000-node chain and confirms an immediate neighbor is found without materializing or fully walking the rest of the chain.
Trade-offs and pitfalls
- Common mistake: checking
node in visitedbefore checkingnode == target; if the target itself somehow ends up added tovisitedon an earlier pass through the loop (for instance, if the visited check were structured differently), the function could miss reporting it as found, ordering the target check first avoids this entirely. - This function only answers existence, not the path itself. If the ACTUAL path is needed, track a parent-pointer map alongside the stack (
came_from[neighbor] = nodeat push time) and reconstruct by walkingcame_frombackward from the target once found. - For REPEATED path-existence queries between many different pairs on the same static graph, computing connected components once (via a single traversal or union-find) and then answering each query as an O(1) "same component" check is far cheaper than re-running a fresh DFS per query.
A debug log statement accidentally includes a raw API key or password in plaintext, and it ships to production before anyone catches it in review. How do you prevent this class of bug systematically, not just rely on catching it in the next code review?
Sample Answer
Direct answer
Treat it as a tooling and process gap, not a reviewer-attention gap: add automated secret scanning at commit time and in CI so a credential-shaped string never merges, and add a logging-layer guard that redacts known secret field names before anything is written out, regardless of what a developer typed. Rotate the exposed credential immediately once found, because a log line, once written, has to be treated as compromised even after the code is fixed.
Structured elaboration
- Prevention layer 1, secret scanning: tools like gitleaks or truffleHog run in pre-commit and CI, catching a credential-shaped string before it's even committed, earlier than code review would.
- Prevention layer 2, structured logging with a deny-list: a logging wrapper that inspects known sensitive field names (password, apiKey, token, authorization) and masks them, so even a developer who forgets is still protected by the framework.
- Prevention layer 3, log-destination scanning: tools on the aggregated log store itself that alert if a secret-shaped pattern appears in ingested logs, catching what slipped past the first two layers.
- Response when it happens anyway: rotate the credential immediately; a log line lives in multiple systems (aggregator, backups, a SIEM), so deleting the log line doesn't undo the exposure.
Worked example
A developer adds a debug line that logs "calling payment API with key: " plus the raw key. A pre-commit gitleaks hook configured with a pattern for the vendor's key format flags the commit before it's ever pushed, well before it would reach a human reviewer or production.
Trade-offs and pitfalls
Deny-list redaction by field name misses a secret logged under an unexpected key or embedded in free text, so it's a safety net, not a guarantee, which is why scanning before merge matters more than redaction at log time. Overly aggressive secret-pattern matching produces false positives that train developers to bypass the scanner, so the patterns need tuning to real credential formats.
What the interviewer probes next
Whether the candidate's answer is "we'd catch it in review," which is exactly what already failed here, versus a systemic, automated, multi-layer answer.
When investigating an incident, how do you weigh quantitative evidence (metrics, logs, traces) against qualitative evidence (engineer interviews, notes) and correlate them into a single timeline? Describe how you would resolve conflicts between the two kinds of evidence when they point to different causes.
Sample Answer
Direct answer
Quantitative evidence (metrics, logs, traces) tells you what happened and when with precision but can miss context and intent; qualitative evidence (engineer interviews, notes, chat logs) fills in the why and the human decision-making, but is subject to memory bias and self-justification. Weigh them together, and when they conflict, treat the disagreement itself as a finding worth investigating rather than picking whichever is more convenient.
Structured elaboration
- Quantitative evidence is precise and timestamped, which makes it the backbone of any timeline, but it can be silent on intent and context: a metric shows latency spiked at 14:03, but not why an engineer chose to deploy at that specific moment or what they believed was true when they did.
- Qualitative evidence captures reasoning and context that logs can't ("I deployed because the dashboard looked fine and I didn't know about the downstream dependency"), but human memory reconstructs events after the fact, often unconsciously smoothing over uncertainty or minimizing one's own role, so it should never override hard timestamped data when the two genuinely conflict.
- Correlating them into one timeline: anchor the timeline on quantitative events (deploys, alerts, metric changes) first, since those are objective and timestamped, then layer qualitative context alongside each event (what the engineer believed, what they were looking at, why they made a given call) as annotation, not as competing facts.
- When they conflict: if an engineer recalls checking a dashboard that logs show wasn't accessed, that's not necessarily dishonesty, memory under stress is genuinely unreliable, but it IS worth investigating why the gap exists: was there a different dashboard, a misremembered timestamp, or a real gap in what was actually checked before the decision was made. The conflict itself, not just its resolution, is often informative about where the process broke down.
Worked example
An engineer recalls seeing a warning-level alert before deploying and deciding it looked minor enough to proceed. Logs show no alert fired until four minutes after the deploy. Rather than concluding the engineer is simply wrong or dismissing the recollection, the investigation digs further and finds the engineer was actually looking at a stale, cached view of the dashboard that hadn't refreshed in several minutes, itself a real and separately worth-fixing gap (a dashboard that can silently show stale data during exactly the moment it matters most). The quantitative record established what actually happened; the qualitative account, once reconciled rather than dismissed, revealed a genuine, previously-unknown contributing factor that the logs alone would never have surfaced.
Trade-offs and pitfalls
The most common mistake is treating quantitative data as always authoritative and qualitative accounts as merely decorative color, which misses genuine contributing factors that only surface through human context. The opposite mistake, treating a confident personal recollection as more reliable than the logs when they conflict, risks building the postmortem's conclusion on a memory distortion. The discipline is to anchor on timestamped data but take conflicting qualitative accounts seriously enough to investigate the gap, not dismiss either source reflexively.
What's the difference between N+1 and N+2 redundancy? For a service normally sized at 10 instances, walk through what each strategy actually buys you in failure tolerance, and when the extra cost of N+2 is worth it.
Sample Answer
Direct answer: N+1 means you provision one spare unit beyond what's needed to serve current load, so the system tolerates exactly one simultaneous failure with zero capacity loss. N+2 provisions two spares, tolerating two simultaneous failures (or one failure plus a second one arriving while the first is still being repaired). For a service sized at 10 instances, N+1 is 11 instances and N+2 is 12; the extra instance in N+2 is worth it when failures are likely to be correlated or when repair (MTTR) is slow enough that a second failure landing during the first one's recovery window is a real possibility, not a hypothetical.
Structured elaboration
- What "N" means: N is the number of units actually required to serve load at your target performance, not the number you happen to run. If 10 instances are needed to handle peak traffic at acceptable latency, N=10.
- N+1: one extra unit. Any single instance, host, rack, or power supply can fail and the system still serves at full capacity from the remaining N. It does not protect against a second, overlapping failure.
- N+2: two extra units. Protects against two simultaneous failures, which matters specifically during the repair window of the first failure (you're running on N+1 capacity while node 1 is being replaced; if node 2 fails during that window, N+1 would drop you below N, but N+2 still covers you).
- When N+2's extra cost is worth it: the decision comes down to how correlated failures are and how long repair takes, not just how critical the service is in the abstract.
Worked example: quantifying the risk N+2 removes
A capacity shortfall only happens when multiple instances are down at the same time, which means the model has to use the instantaneous probability that an instance is down at any given moment, not the probability that it fails at some point during the year (an annual failure probability answers a different question and silently ignores repair-window overlap). The right building block is the instantaneous-unavailability formula: at any random moment, the fraction of time a single instance has historically spent broken and being repaired is MTTR divided by the full working-plus-repair cycle, MTBF+MTTR, which is exactly the probability that instance happens to be down at an arbitrary moment in time:
q=MTBF+MTTRMTTRPin illustrative values: each instance has an MTBF of 8,760 hours (fails on average about once a year) and an MTTR of 4 hours (time to detect and replace or restart a failed instance). Then:
q=8760+44=87644≈0.000456(0.0456%)That's the probability any single instance is down (mid-repair) at a random moment.
For N+1 (11 total instances), capacity drops below the needed N=10 only if 2 or more instances are down simultaneously:
P(down≥2∣n=11,q)=1−(011)(1−q)11−(111)q(1−q)10 =1−0.994991−0.004998=0.0000114(0.00114%)For N+2 (12 total instances), capacity drops below 10 only if 3 or more are down simultaneously:
P(down≥3∣n=12,q)=1−k=0∑2(k12)qk(1−q)12−k =1−0.994537−0.005450−0.0000137=0.0000000209(0.0000021%)Both numbers are tiny snapshot probabilities; the more useful reading is as the expected fraction of the year the system spends in a shortfall state, converted into expected annual downtime minutes by taking that same fraction-of-time-in-shortfall and multiplying it by the number of minutes in a year, 525,600 (365 days x 24 hours x 60 minutes), the standard way a fraction-of-time becomes an annual downtime figure:
N+1: 0.0000114×525,600≈6.0 minutes/year N+2: 0.0000000209×525,600≈0.011 minutes/year(≈0.66 seconds/year)So under this repair-window-conditioned model, N+1 carries about 6 minutes/year of expected capacity-shortfall exposure, and N+2 cuts that to about 0.01 minutes/year, roughly a 548x reduction, not because any instance's individual failure rate changed, but because a shortfall now requires a second failure to land inside the narrow repair window of the first, and adding a spare pushes that bar from "2 simultaneous" to "3 simultaneous," a much rarer event once q is small. Whether that ~6-minute-a-year difference is worth one extra instance's cost is exactly the trade-off to walk through out loud: for a service where even a few minutes of capacity shortfall risks an SLA breach, cutting expected exposure by roughly two and a half orders of magnitude for one extra instance is usually cheap insurance; for an internal batch service, shortfall risk this small to begin with is very likely not worth the extra spend.
Common concrete instances of the same reasoning: UPS/power-supply sizing (N+1 power modules in a rack survive one PSU failure; N+2 covers one failed unit plus one more failing during the swap), and network device sizing (N+1 top-of-rack switches vs N+2 when switch firmware upgrades take units offline for extended maintenance windows, effectively acting like a "planned failure" that N+1 alone can't absorb if an unplanned one happens at the same time).
Trade-offs & pitfalls
- N+2 isn't "more reliable" in a vacuum, it's specifically insurance against overlapping failures; if your MTTR is minutes and failures are rare and independent, N+1 is usually sufficient and N+2 is paying for a scenario that almost never occurs.
- Fault-domain correlation matters more than the raw redundancy count: N+1 spread across a single rack doesn't protect against a rack-level power failure taking out several "independent" instances at once; redundancy has to be placed in genuinely independent failure domains (different racks, AZs, or power feeds) or the N+1/N+2 math above doesn't hold, because the independence assumption breaks.
- A common wrong turn: treating N+1 as "one extra instance total" when instances are correlated (e.g., all on the same physical host or the same AZ). The formula only protects capacity if the spare's failure mode is independent of the others.
- N+2 costs roughly 20% more standing capacity than N+1's 10% here; that recurring cost has to be justified against the downtime cost it avoids, not assumed.
After a working meeting, write a concise summary (3-6 sentences) that captures the decision made, who owns each follow-up, the deadlines, and any question that is still open.
Sample Answer
Direct answer
Write a short summary right after the meeting that states the decision made, names an owner and deadline for each follow-up, and flags anything still unresolved, so nobody has to reconstruct what happened from memory a week later.
Structured elaboration
- State the decision first, in one sentence, even if it feels obvious right after the meeting; it stops being obvious within a day or two, especially for people who weren't in the room.
- List action items with an owner and a deadline each, not a bare to-do list; "someone should look into X" is not actionable, "Priya will check the vendor SLA by Thursday" is.
- Name what's still open, explicitly, rather than letting it quietly drop; a one-line "not yet decided: whether we notify customers proactively" prevents someone assuming it was implicitly settled.
- Send it promptly, ideally within the hour, while the details are fresh and before people have moved on to something else and stopped tracking it mentally.
- Keep it short. Three to six sentences is usually enough; a summary that's as long as a transcript won't get read.
Worked example
"Decision: we're moving the schema migration to next Tuesday's low-traffic window instead of doing it live this week. Action items: Priya to update the migration runbook by Monday EOD; Sam to notify the on-call rotation of the new window by Friday. Open question: whether we need a customer-facing heads-up, still deciding, will confirm by Wednesday."
Three sentences, one decision, two owned action items with deadlines, and one explicitly flagged open item.
Trade-offs and pitfalls
- The most common failure is writing a summary that lists what was discussed instead of what was decided; a meeting can generate a page of discussion and one real decision, and the summary should reflect that ratio.
- An action item without a named owner tends to silently not get done; if you can't name an owner in the summary, that's a sign the meeting didn't actually resolve who's responsible.
- Sending it too late (days later) defeats the purpose; by then people have already formed their own, sometimes conflicting, memory of what was agreed.
Recommended Additional Resources
- Google SRE Book (site.sre.google) - Free online resource covering SRE principles, monitoring, incident response, and blameless postmortems
- SRE Workbook - Practical exercises and case studies on reliability practices
- Designing Data-Intensive Applications by Martin Kleppmann - Essential for understanding distributed systems, scalability, and reliability trade-offs
- Building Secure and Reliable Systems by Heather Adkins et al. - Covers SRE practices at Google scale
- Unix and Linux System Administration Handbook by Evi Nemeth et al. - Comprehensive reference for Linux internals and administration
- The Linux Systems Interview by Marker Kane - SRE-focused Linux interview preparation
- Computer Networking: A Top-Down Approach by Kurose & Ross - Foundational networking concepts and protocols
- LeetCode - Practice coding problems with filters for arrays, graphs, and two-pointer techniques
- sadservers.com - Practical Linux troubleshooting scenarios that simulate real production issues
- Neetcode System Design course - Beginner-friendly system design fundamentals
- ByteByteGo YouTube channel - Visual explanations of system design and algorithms
- Glassdoor and Blind - Read recent interview experiences from candidates who interviewed at Apple for SRE roles to understand current trends
Search Results
[06-2024][Apple SRE Interview Experience: A Journey to Success
First Round: Phone Screen with Hiring Manager (Early April) · Coding: LeetCode Easy problem using two-pointer technique · Linux/Systems: Deep ...
Apple SRE Interview Experience (Offer) - Software Engineering - Blind
Total process took 6 months, 3 months to reply to initial application (with referral), 1 month after completing interviews to get offer, 7 rounds total.
Apple Entry level SRE Interview - InterviewHelp
What to Expect in the Interview · 1. General Role Discussion · 2. Experience Discussion · 3. Coding Challenge on CoderPad.
Senior Engineer's Guide to Apple Interviews + Questions
The recruiter spends 30 minutes or an hour per debrief where engineers are talking about the details about the code. So if the recruiter is paying attention or ...
Apple Site Reliability Engineer Interview: Process + Questions
This is the core loop: multiple back-to-back interviews (often 3 rounds) covering a mix of reliable systems design, troubleshooting scenario, ...
This interview preparation guide was generated using AI-powered research from the sources listed above. While we strive for accuracy, we recommend verifying critical information from official company sources.
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths
Browse Site Reliability Engineer (SRE) jobs
AI-enriched listings across hundreds of company career pages
Explore Jobs