Real Interview Questions
This module contains actual interview questions from infrastructure, observability, and platform engineering roles at top companies. Each question includes detailed solutions and the key insights interviewers are looking for. Think of this module the way a kernel developer thinks about test suites: you don’t just verify the happy path — you test the boundary conditions, the failure modes, and the performance under pressure. These questions work the same way. They test whether you have a working mental model of the kernel, not whether you memorized a man page.Interview Format: Usually 45-60 minute deep dives into 2-3 topics
Time to Prepare: 10-12 hours to work through all scenarios
Company-Specific Patterns
Different companies emphasize different areas:Observability Company Questions
Question 1: Implement a Syscall Counter (Datadog-style)
Discussion Points
Discussion Points
- Knowledge of different approaches (strace, perf, eBPF)
- Understanding of overhead implications
- Production safety considerations
- Sampling vs complete counting trade-offs
- strace: Per-syscall ptrace, very high overhead (~100x slowdown)
- perf: Sampling-based, lower overhead, may miss syscalls
- eBPF tracepoints: Low overhead (~1-5%), production-safe
- eBPF kprobes: Slightly higher overhead, more flexible
Solution Approach
Solution Approach
strace uses ptrace, which forces a context switch on every single syscall — the kernel stops the traced process, notifies the tracer, waits for it to resume, then re-enters the process. At scale, that is a 100x slowdown. eBPF tracepoints, by contrast, run a small verified program inside the kernel at the tracepoint site — no context switches, no copying data to user space on every event.- Periodically read the map (every 1-2 seconds), sort by count, display top N processes
- Handle PID reuse: cross-reference with
/proc/<pid>/statstart time so a recycled PID doesn’t inherit the old count - Optionally reset counters each interval for a “rate” view (syscalls/sec per process)
Production Considerations
Production Considerations
- Bounded map size: Won’t consume unlimited memory
- No locks in hot path: Per-CPU increments would be ideal
- Graceful degradation: If map full, just skip new PIDs
- Low overhead: Tracepoint, not ptrace
- ~50-100ns per syscall (compare to ptrace: ~10-50us per syscall)
- On a busy system (100K syscalls/sec): ~1% CPU overhead
- On an extremely busy system (1M syscalls/sec): ~5-10% CPU — at this point, consider sampling
- Memory footprint: 10240 entries * (4 + 8) bytes * NR_CPUS — on a 64-core machine, about 7.5 MB
- Acceptable for production monitoring, but always set an upper bound on map size
Question 2: Debug High Latency in Production
Investigation Framework
Investigation Framework
-
Characterize the problem:
- When do spikes occur? (Time correlation)
- Which requests are affected? (Endpoint, payload)
- Duration of spikes? (Seconds, minutes)
-
Gather baseline metrics:
- CPU utilization (is there contention?)
- Memory usage (swapping? GC?)
- Disk I/O (latency, throughput)
- Network (retransmits, latency)
-
Narrow down the layer:
- Application code?
- Runtime (GC pauses)?
- Kernel (scheduling, I/O)?
- Hardware (disk, network)?
Tools and Commands
Tools and Commands
Common Causes and Solutions
Common Causes and Solutions
-
Garbage Collection:
- Symptom: Regular, predictable spikes
- Detection: GC logs show long pauses
- Fix: Tune GC, reduce allocation rate
-
Disk I/O:
- Symptom: Correlates with writes/fsync
- Detection:
biolatencyshows spikes - Fix: Async I/O, better storage
-
Memory Pressure:
- Symptom: During memory spikes
- Detection:
sar -Bshows page faults - Fix: Increase memory, reduce footprint
-
CPU Throttling (containers):
- Symptom: Regular, consistent spikes
- Detection:
cat /sys/fs/cgroup/cpu.stat - Fix: Increase CPU limits
-
Network Issues:
- Symptom: Affects network calls
- Detection:
tcpretrans,ss -ti - Fix: Check network path, timeouts
Question 3: Container Memory Behavior
The Question: “Explain what happens when a container hits its memory limit. What are the different behaviors, and how would you debug an OOM-killed container?”Memory Limit Behavior
Memory Limit Behavior
memory.current: Current usage in bytes — what the cgroup is actually consuming right nowmemory.max: Hard limit (OOM if exceeded) — the absolute ceiling, no negotiationmemory.high: Soft limit (throttling begins) — the kernel starts pushing back but does not killmemory.low: Best-effort protection — the kernel tries not to reclaim from this cgroup when the system is under pressure, but will if there is no other choicememory.min: Hard protection — the kernel will never reclaim below this watermark, even under extreme system-wide pressure. Use this for critical workloads that must not be evicted.
Debugging OOM Kills
Debugging OOM Kills
Prevention Strategies
Prevention Strategies
- Profile application under realistic load — not
curl localhost, but actual production traffic replayed against a staging environment - Account for peak usage, not average — the OOM killer does not care about your P50; it cares about your P100
- Include headroom for GC (JVM needs ~2x heap for GC overhead), file cache (the kernel will use any free memory in the cgroup for page cache), and kernel slab allocations
- Monitor
memory.highthrottling events — they are the early warning that your limit is too tight
- Set JVM heap below container limit:
-Xmxshould be ~75% ofmemory.maxto leave room for non-heap memory (thread stacks, native memory, class metadata). A common formula:-Xmx = container_limit * 0.75 - Use memory-aware allocators like jemalloc or tcmalloc that return memory to the OS more aggressively than glibc’s default allocator (which holds onto freed pages via
brk) - Implement backpressure mechanisms: when memory usage exceeds a threshold, stop accepting new work. This is cheaper than being OOM-killed and restarting.
Infrastructure Company Questions
Question 4: Network Stack Performance (Cloudflare-style)
The Question: “Explain the journey of a packet from the NIC to the application. Where are the performance bottlenecks, and how would you optimize for high packet rates?”Packet Journey
Packet Journey
Performance Bottlenecks
Performance Bottlenecks
-
Interrupt overhead:
- Each interrupt: ~1-2μs
- At 1M pps: 100% CPU just handling interrupts
- Solution: NAPI, interrupt coalescing
-
Memory allocation:
- sk_buff allocation per packet
- Solution: Page pools, recycling
-
Lock contention:
- Socket lock for each packet
- Solution: SO_REUSEPORT, RSS
-
Cache misses:
- Packet data not in cache
- Solution: Busy polling, NUMA awareness
-
Context switches:
- Waking application per packet
- Solution: Batching, busy polling
Optimization Techniques
Optimization Techniques
- Process packets at the driver level, before
sk_buffallocation —sk_buffallocation is one of the most expensive per-packet operations in the kernel (~100ns each) - Achievable throughput: 10M+ pps on a single core (vs ~1M pps through the normal stack)
- Used by Cloudflare (DDoS mitigation), Meta (load balancing with Katran), and Cilium (Kubernetes networking)
- Trade-off: you must write your packet processing logic in eBPF, which means no TCP/IP stack, no sockets — you are essentially writing a custom NIC firmware in a safe sandbox
Question 5: CPU Isolation for Low Latency
The Question: “We need sub-millisecond latency for a trading system. How would you configure Linux to minimize jitter?”Sources of Jitter
Sources of Jitter
- Timer interrupts (every 1-4ms)
- RCU callbacks
- Kernel threads (kworker, ksoftirqd)
- System call overhead
- SMI (System Management Interrupt)
- Cache pollution
- NUMA remote access
- Power management (C-states)
Isolation Configuration
Isolation Configuration
Verification and Testing
Verification and Testing
System Design with Kernel Awareness
Question 6: Design a Container Metrics Collector
The Question: “Design a system to collect CPU, memory, and I/O metrics from 10,000 containers on each host with minimal overhead.”Architecture Overview
Architecture Overview
Optimized Implementation
Optimized Implementation
Overhead Analysis
Overhead Analysis
- 30K file reads per second
- ~100μs per read = 3 seconds of CPU
- Too much overhead!
- Keep FDs open: eliminate open/close
- Batch reads with io_uring
- Stagger collection across time
- Result: ~100ms of CPU per second
- Constant overhead regardless of container count
- ~1-2% CPU for tracing hooks
- Scales to any number of containers
- eBPF for high-frequency (CPU, I/O): ~1% overhead
- Polling for low-frequency (limits, configs): ~0.1% overhead
- Total: ~1.1% CPU overhead for 10K containers
Debugging Scenarios
Scenario 1: Container Not Starting
Situation: A container fails to start with “permission denied” but works as root.Debugging Steps
Debugging Steps
Common Causes
Common Causes
-
Seccomp blocking syscall:
- Solution: Add syscall to profile or use
--security-opt seccomp=unconfined
- Solution: Add syscall to profile or use
-
Missing capability:
- Solution:
--cap-add=SYS_ADMIN(or specific capability)
- Solution:
-
SELinux/AppArmor denial:
- Solution: Check audit logs, update policy
-
User namespace UID mapping:
- Solution: Check /etc/subuid, /etc/subgid
-
Read-only filesystem:
- Solution:
--read-onlywith appropriate tmpfs mounts
- Solution:
Scenario 2: High Memory Usage Mystery
Situation: Container shows 2GB used, but application reports only 500MB heap. This is one of the most common debugging scenarios in container platforms, and the answer lies in understanding what the kernel counts as “memory used by this cgroup” versus what an application considers “my memory.”Memory Accounting Deep Dive
Memory Accounting Deep Dive
-
Page cache (accounts for 60-80% of these cases):
- Files read by the application are cached in memory by the kernel
- Shows in
memory.currentbut NOT in the application’s heap metrics - The good news: these pages are reclaimable — the kernel will evict them under pressure
- The bad news: they still count toward
memory.maxand can trigger OOM if the limit is tight
-
Memory-mapped files (shared libraries, data files):
- Every
.soloaded by the application is mmap’d into the process address space - Only the resident pages (those actually touched) consume physical memory
- A 200MB library might only have 20MB resident — but those 20MB are charged
- Every
-
Slab memory (kernel allocations on behalf of this cgroup):
- Network buffers (
sk_buff), file system metadata (dentry, inode caches), socket structures - A container running a web server with 10K connections could have 50-100MB of kernel slab memory
- This is completely invisible to the application but fully charged to the cgroup
- Network buffers (
-
Shared memory / tmpfs:
/dev/shmusage, POSIX shared memory, and tmpfs mounts are charged as shmem- Many databases and ML frameworks allocate large shared memory segments
- Charged once to the cgroup even if multiple processes access it
Quick Reference: Commands for Interviews
Key Interview Tips
Think Out Loud
Start Simple
Know the Stack
Practice Debugging
perf sched latency first.” This shows you are reasoning, not just running commands from a list.Interview Deep-Dive
A production service is leaking file descriptors. How do you find the leak and what kernel mechanisms are involved?
A production service is leaking file descriptors. How do you find the leak and what kernel mechanisms are involved?
struct files_struct (pointed to by task->files) that contains an fd table — an array of struct file * pointers. Each open() allocates the lowest available fd and creates a struct file backed by a dentry/inode pair in the VFS. If close() is never called, the struct file is never freed, the dentry reference count stays elevated, and the inode stays pinned in memory.Diagnosis steps:ulimit -n, backed by RLIMIT_NOFILE) defaults to 1024 on many systems. When you hit it, every open(), socket(), accept(), and pipe() returns -EMFILE. This cascading failure is how a file descriptor leak in one component can crash an entire service — the process cannot open any new files, including log files to report the error.Common mistake: Candidates suggest “just restart the process.” In production, restarting a stateful service (database, queue) can cause data loss or require lengthy recovery. The right answer is to identify the leak, apply a mitigation (raise the fd limit temporarily), then deploy a fix.Explain what happens in the kernel when a process calls fork() followed by exec(). Why is copy-on-write important here?
Explain what happens in the kernel when a process calls fork() followed by exec(). Why is copy-on-write important here?
fork() is called, the kernel’s do_fork() (or kernel_clone() in modern kernels) creates a new task_struct — the kernel’s representation of a process. But it does NOT copy the parent’s physical memory. Instead, it duplicates the page table entries and marks every writable page as read-only in both the parent and the child. This is copy-on-write (COW).When either process later tries to write to a COW page, the CPU raises a page fault (because the PTE is marked read-only). The kernel’s page fault handler (do_wp_page()) detects the COW condition, allocates a new physical page, copies the content, and updates the faulting process’s page table to point to the new page with write permission. The other process still points to the original page.When exec() is called, the kernel’s do_execve() completely replaces the process’s address space. It:- Releases all existing VMAs (which drops the refcounts on all those COW pages)
- Parses the ELF binary header to determine the program’s segments
- Maps the text segment (code) as read-only + executable
- Maps the data segment as read-write
- Sets up a new stack, copies
argvandenvponto it - Points the instruction pointer to the ELF entry point
fork() would copy all of the parent’s memory. A 2GB process forking would require 2GB of allocation and copying before the child can even call exec(), which immediately discards all of it. COW makes fork() nearly instant (just duplicating page tables, ~100us for a large process) and defers the actual copy to only the pages that are actually written.What impresses interviewers: Mention the MAP_PRIVATE flag on mmap’d files uses the same COW mechanism. Also mention that this is why vfork() exists — it is an even cheaper fork that shares the parent’s address space entirely (no page table copy), but the child MUST call exec() or _exit() immediately. posix_spawn() is the modern alternative that avoids the fork+exec overhead entirely.You notice a Kubernetes pod is being throttled despite showing low average CPU usage. What is happening and how do you fix it?
You notice a Kubernetes pod is being throttled despite showing low average CPU usage. What is happening and how do you fix it?
500m (half a core), the kernel translates this to cpu.max = "50000 100000" — meaning the cgroup gets 50,000 microseconds of CPU time per 100,000-microsecond period.Here is where averages are deceptive. If your application is bursty — idle for 80ms, then needs 100% of a core for 20ms — it will consume its entire 50ms quota in the first 20ms of burst, then be throttled for the remaining 80ms of that period. The average CPU usage over a minute might show 10%, but the application experiences hard throttling during every burst.How to detect it:- Increase the CPU limit — the most straightforward fix, but increases cost
- Set requests = limits (Guaranteed QoS class) — gives the pod a dedicated CPU core via cpuset pinning, which eliminates the CFS bandwidth controller entirely
- Increase only the period — some Kubernetes distributions expose
--cpu-cfs-quota-period. A longer period (e.g., 200ms) allows longer bursts before throttling - Remove the limit entirely (set requests only) — controversial, but it means the pod runs in Burstable QoS and is never throttled. The risk is noisy-neighbor effects on shared nodes.
nr_throttled / nr_periods is the metric to watch, not average CPU usage. A ratio above 5% indicates that the application is being actively harmed by its CPU limit. Also mention that in multi-threaded applications, a limit of 1000m (1 core) does NOT mean “use one core” — it means “use 100ms of CPU time per 100ms period, spread across ANY number of cores.” A 4-thread application with a 1-core limit will burn through its quota in 25ms and be throttled for 75ms.How does the kernel's OOM killer decide which process to kill, and how can you influence its decision in a container environment?
How does the kernel's OOM killer decide which process to kill, and how can you influence its decision in a container environment?
out_of_memory() in mm/oom_kill.c. In a cgroup context, the OOM killer is scoped to the cgroup that exceeded its limit — it will not kill processes outside the cgroup.The kernel scores each process using oom_badness(), which calculates:oom_score_adj (a value from -1000 to +1000, set via /proc/<pid>/oom_score_adj):oom_score_adj = -1000: Never kill this process (OOM-immune)oom_score_adj = 0: Default scoringoom_score_adj = +1000: Always kill this process first
/proc/<pid>/oom_score (range 0-1000). The process with the highest score is killed.In container environments:- Kubernetes sets
oom_score_adjbased on QoS class: Guaranteed = -997, Burstable = 2-999, BestEffort = 1000 - This means BestEffort pods are killed first, then Burstable, and Guaranteed pods are killed last
- You can also set
memory.oom.group = 1in cgroup v2 to kill ALL processes in the cgroup together (useful for ensuring a clean restart rather than partial kills that leave the container in a broken state)
memory.oom.group in cgroup v2 and why it matters for multi-process containers like those running an init system or a sidecar pattern.Walk me through what happens in the kernel between a user calling write() on a file and the data being on disk.
Walk me through what happens in the kernel between a user calling write() on a file and the data being on disk.
write() to disk traverses four major kernel subsystems:1. VFS layer (ksys_write -> vfs_write): The kernel resolves the fd to a struct file, which points to the filesystem’s file_operations struct. It calls the filesystem’s .write_iter handler (e.g., ext4_file_write_iter).2. Page cache (generic_perform_write): The filesystem writes data into page cache pages. It finds (or allocates) the page corresponding to the file offset, copies the user’s data into the page, and marks the page as dirty. At this point, write() returns to user space. The data is in memory but NOT on disk.3. Writeback (writeback_single_inode): The kernel’s flusher threads (or the sync syscall) periodically walk the list of dirty inodes and submit their dirty pages to the block layer. The default writeback delay is 30 seconds (dirty_writeback_centisecs), but it can be triggered earlier if the percentage of dirty pages exceeds dirty_ratio.4. Block I/O layer (submit_bio -> device driver -> disk): The block layer converts page writes into block I/O requests (struct bio), merges adjacent requests (elevator/IO scheduler), and submits them to the device driver. The driver programs the hardware (DMA for NVMe, SCSI commands for SAS) and the data is written to the physical medium.Critical insight: write() returning success does NOT mean the data is on disk. It means the data is in page cache. If the machine loses power before writeback completes, the data is lost. This is why databases call fsync() — which forces the dirty pages for that file through the entire path to the disk’s persistent storage. fsync() does not return until the drive’s write cache has been flushed.What impresses interviewers: Mention O_DIRECT as the bypass for page cache (used by databases that manage their own caching), O_DSYNC for synchronous data writes (like write + fdatasync on every call), and that NVMe drives with power-loss protection can safely report fsync completion before data hits NAND because the drive’s capacitors can flush the write cache during power loss. Also mention that io_uring changes the game by allowing the kernel to batch and submit these operations asynchronously without per-syscall overhead.Next: Hands-on Projects →