The 60 Linux questions interviewers actually ask, with direct answers, runnable commands, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.
60 questions with answersKey Takeaways
Linux is a free, open-source operating system kernel first released by Linus Torvalds in 1991. On its own the kernel manages hardware, memory, processes, and the file system; combined with GNU userland tools, an init system, and packages, it becomes a distribution you can actually run, like Ubuntu, Debian, Fedora, or Red Hat Enterprise Linux. It's Unix-like, which means it inherits the everything-is-a-file model, a permission system, and a shell that composes small programs into pipelines. According to the official Linux Kernel documentation, the kernel is what schedules processes, drives hardware, and mediates every system call, while the distribution wraps it in the tools you type every day. In interviews, Linux questions probe the shell, the file system layout, permissions, process and signal handling, and networking basics, and they favor understanding over memorized flags. This page collects the 60 questions that come up most, each with a direct answer and runnable commands. The first technical round is increasingly a live coding interview, so pair this bank with our AI interview preparation guides for the format side.
Watch: Introduction to Linux: Full Course for Beginners
Video: Introduction to Linux: Full Course for Beginners (freeCodeCamp.org, YouTube)
Test yourself and earn a certificate
10 quick questions. Score 70%+ to download your Linux certificate.
The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.
Linux is the kernel: the core program that manages hardware, memory, processes, and the file system. A distribution is the kernel plus everything you actually use, GNU tools, a shell, a package manager, and default software, bundled and maintained together.
So Ubuntu, Debian, and Fedora all run the same Linux kernel but differ in package managers, release cadence, and defaults. When someone says they run Linux, they mean a distribution built on the kernel.
Key point: Interviewers open with this to hear whether you know kernel versus distribution. Getting that split right early sets the tone.
Everything hangs off a single root, written /. There are no drive letters like on Windows; disks and partitions mount into the tree at chosen points. Each top-level directory has a defined job, set by a shared standard, so any Linux system is navigable the same way once you know the map.
Knowing the map means you find config in /etc, logs in /var/log, and user files in /home without guessing.
ls / # root: bin etc home var usr tmp dev proc ...
cat /etc/os-release # which distribution and version
ls /var/log # system and application logs live hereKey point: The three or four directories with their purpose. Reciting all of them indicates memorized; explaining a few indicates used.
Watch a deeper explanation
Video: Linux Directories Explained in 100 Seconds (Fireship, YouTube)
An absolute path starts from root and always begins with /, like /home/asha/report.txt. It means the same thing no matter where you are. A relative path starts from your current directory, like ../logs/app.log, so its meaning depends on where you run it.
The shortcuts matter too: . is the current directory, .. is the parent, ~ is your home directory.
cd /var/log # absolute: same target from anywhere
cd ../lib # relative: goes to /var/lib from /var/log
cd ~ # home directory shortcut
pwd # confirm where you landedls lists directory contents. On its own it shows names; the flags are where the real work happens. -l gives the long format with permissions, owner, size, and time; -a shows hidden dotfiles; -h makes sizes human-readable; -t sorts by modification time.
Combining them is normal: ls -lah is one of the most typed commands on any Linux box.
ls -l # long listing: permissions, owner, size, date
ls -a # include hidden files like .bashrc
ls -lah # long, all files, human-readable sizes
ls -lt # newest firstWatch a deeper explanation
Video: 60 Linux Commands you NEED to know (in 10 minutes) (NetworkChuck, YouTube)
Every file carries three permission sets: one for the owner, one for the group, and one for everyone else. Each set has three bits: read (r), write (w), and execute (x). ls -l prints them as a 10-character string like -rwxr-xr--, where the leading character is the file type and the next nine are those three sets in order.
Read lets you view a file or list a directory; write lets you change a file or add entries to a directory; execute lets you run a file or enter a directory. On directories, x is what lets you cd in.
| Symbol | On a file | On a directory |
|---|---|---|
| r (read) | View contents | List entries |
| w (write) | Modify contents | Add or remove entries |
| x (execute) | Run the file | Enter (cd into) it |
Key point: The follow-up is almost always 'what does x mean on a directory?'. Have the cd-in answer ready; many candidates only know the file meaning.
chmod sets permissions. In numeric mode, each of the three sets (owner, group, others) becomes a single digit built by adding read=4, write=2, and execute=1. So 7 is rwx, 5 is r-x, 4 is r--, and 6 is rw-. You read the three digits left to right as owner, group, others, which is why one number describes the whole file.
chmod 644 is the normal permission for a regular file (owner reads and writes, everyone else reads). chmod 755 is normal for scripts and directories (owner does everything, others read and execute).
chmod 644 notes.txt # rw-r--r--
chmod 755 deploy.sh # rwxr-xr-x, runnable by all
chmod u+x deploy.sh # symbolic mode: add execute for owner
chmod -R 755 public/ # recurse into a directory treeKey point: Being able to translate 644 and 755 to rwx out loud is the bar. If you hesitate, practice the 4-2-1 addition until it's instant.
Watch a deeper explanation
Video: How to use chmod: Manage File Permissions in Linux (Akamai Developers, YouTube)
chown changes a file's owner and, with the owner:group form, its group too; chgrp changes only the group. Ownership decides which of the three permission sets applies to you, so a 'permission denied' error is often an ownership problem, not a mode-bits problem. Fixing the owner can matter more than fixing the rwx flags.
You usually need root (sudo) to give a file to another user.
sudo chown asha report.txt # change owner
sudo chown asha:devs report.txt # owner and group
sudo chgrp devs report.txt # group only
sudo chown -R asha:devs /srv/app # recursecat dumps the whole file to the screen, fine for short files and for concatenating. less pages through a large file without loading it all, with search and scrolling. head shows the first lines, tail the last, and tail -f follows a file as it grows, which is how you watch a log live.
cat config.ini # print the whole file
less /var/log/syslog # page through, q to quit
head -n 20 access.log # first 20 lines
tail -f /var/log/app.log # follow new lines as they arriveKey point: tail -f for live logs matters.
grep searches text for lines matching a pattern and prints the ones that match. It's the workhorse for finding a string inside files, or for filtering the output of another command through a pipe. You give it a pattern and one or more files, or you feed it a stream, and it keeps only the lines you asked for.
The flags that matter: -i ignores case, -r recurses through directories, -n shows line numbers, -v inverts the match, and -E enables extended regular expressions.
grep "error" app.log # lines containing error
grep -i "error" app.log # case-insensitive
grep -rn "TODO" src/ # recurse, show line numbers
ps aux | grep nginx # filter another command's outputA pipe (|) sends one command's standard output into the next command's standard input, so you chain small tools into a bigger job. Redirection sends output to or from files instead: > writes (overwriting), >> appends, and < reads input from a file.
Every process starts with three streams: stdin (0), stdout (1), and stderr (2). Understanding those numbers is what lets you route errors separately from normal output.
cat access.log | grep 500 | wc -l # count 500 errors
echo "started" > run.log # overwrite
echo "step 2" >> run.log # append
make build > out.txt 2> err.txt # split stdout and stderrKey point: The clean way to show mastery: explain 2>&1 and why 'command > file 2>&1' differs from 'command 2>&1 > file'. Order of redirects matters.
Watch a deeper explanation
Video: Linux Tutorial - Basic Command Line (Traversy Media, YouTube)
ps shows a snapshot of processes; ps aux is the common form listing every process with its user, CPU, memory, and command. top and htop give a live, sorted view that updates. Each process has a PID, and that PID is what you use to inspect or stop it.
ps aux # every process, one snapshot
ps aux | grep python # find a specific process
top # live view, q to quit
pgrep -a nginx # PIDs matching a namekill sends a signal to a process by PID. Plain kill sends SIGTERM (15), which politely asks the process to clean up and exit; a well-behaved program flushes buffers and closes files first. kill -9 sends SIGKILL, which the kernel enforces immediately and the process can't catch or ignore.
Reach for SIGTERM first. Use SIGKILL only when a process is stuck and ignoring the polite request, because it gives the program no chance to shut down cleanly.
kill 4821 # SIGTERM: ask it to exit cleanly
kill -15 4821 # same thing, explicit
kill -9 4821 # SIGKILL: force, no cleanup
pkill nginx # by name instead of PIDKey point: Leading with 'try SIGTERM first, SIGKILL is the last resort' is exactly the judgment this question screens for.
find walks a directory tree and matches on criteria: name, type, size, modification time, permissions, owner. It can also run an action on each match with -exec or -delete, which makes it a tool for bulk operations, not just search.
find . -name "*.log" # by name, current tree
find /var -type f -size +100M # files over 100 MB
find . -mtime -1 # modified in last 24h
find /tmp -name "*.tmp" -delete # find and deleteKey point: If they ask 'find versus locate', the technical answer is find walks the live tree (accurate, slower) while locate reads a prebuilt database (fast, possibly stale).
cp copies files, mv moves or renames them, and rm removes them. Directories need the -r (recursive) flag for cp and rm because they operate on files by default. The flag that saves careers is -i (interactive), which prompts you before overwriting or deleting, giving you a chance to catch a wrong path.
rm -rf is fast and unforgiving: it deletes recursively and forcefully with no prompt and no trash. Say out loud that you double-check the path before running it.
cp -r src/ backup/ # copy a directory
mv old.txt new.txt # rename
mv report.txt ~/docs/ # move
rm -i notes.txt # prompt before deleting
rm -rf build/ # recursive force: no undoman opens the manual page for a command, with its options and usage. --help gives a shorter summary printed by the command itself. For a one-line description of what a command is, use whatis, and to find commands by keyword use apropos.
Knowing where the answer lives beats memorizing every flag. Interviewers like candidates who say 'I'd check the man page' instead of guessing.
man ls # full manual, q to quit
ls --help # quick option summary
whatis grep # one-line description
apropos network # commands related to a keywordroot is the superuser (UID 0) with unrestricted access to every file and command. sudo runs a single command with raised privileges, checked against a policy in /etc/sudoers, so admins get root access for one command without logging in as root.
The practice reason: sudo logs who ran what, and limits blast radius. Running as root all day is how a typo wipes a system.
sudo systemctl restart nginx # one command as root
whoami # your user
sudo whoami # root
sudo -l # what you're allowed to runEnvironment variables are named values that the shell and the programs it launches read for configuration. PATH lists the directories searched for commands, HOME points to your home directory, and applications read their own, like DATABASE_URL. You print one with echo $VAR and list them all with env, and every child process inherits the exported ones.
Set for one command inline, export for the current shell and its children, or add to a shell startup file like ~/.bashrc to make it persistent.
echo $PATH # where the shell looks for commands
export API_KEY="abc123" # available to child processes
API_KEY=abc123 python app.py # only for this one command
printenv HOME # print a single variableThrough a package manager, which resolves dependencies and installs from trusted repositories rather than by downloading random binaries. Debian and Ubuntu use apt with .deb packages; Fedora and Red Hat use dnf with .rpm packages; Arch uses pacman. Each one fetches, verifies, and installs the software plus anything it needs to run.
The pattern is the same everywhere: update the package index, then install, remove, or upgrade. Knowing your target distro's manager is table stakes for a Linux role.
| Task | Debian / Ubuntu (apt) | Fedora / RHEL (dnf) |
|---|---|---|
| Refresh index | sudo apt update | sudo dnf check-update |
| Install | sudo apt install nginx | sudo dnf install nginx |
| Remove | sudo apt remove nginx | sudo dnf remove nginx |
| Upgrade all | sudo apt upgrade | sudo dnf upgrade |
A hard link is another directory entry pointing to the same inode (the same actual data on disk). Delete one name and the data survives as long as any hard link remains. A symbolic link (symlink) is a small file that stores a path to another file; delete the target and the symlink dangles.
Practical differences: hard links can't cross file systems or point to directories; symlinks can do both but break if the target moves.
ln original.txt hardlink.txt # same inode
ln -s original.txt softlink.txt # stores the path
ls -li original.txt hardlink.txt # same inode number
readlink softlink.txt # shows the target pathKey point: The clean tell of understanding: 'a symlink points to a name, a hard link points to the data.' Mention that ls -i reveals shared inodes.
Globbing lets the shell expand a pattern into a list of matching filenames before a command even runs. The star * matches any run of characters, ? matches exactly one character, [abc] matches one character from a set, and {a,b} expands into each option in turn. The command receives the expanded filenames, not the pattern.
The shell does the expansion, not the command, so 'ls *.txt' hands ls the actual list of matches. That's why an unmatched pattern sometimes passes through literally.
ls *.log # every .log file
ls report?.txt # report1.txt, reportA.txt, not report10.txt
ls img_[0-9].png # img_0.png through img_9.png
cp file.{txt,bak} # brace expansion into two namesdf reports free and used space for each mounted file system, and df -h makes those numbers human-readable in gigabytes. du reports how much space specific files and directories consume, and du -sh gives a single summarized, human-readable total for a path. df answers 'is the disk full', du answers 'what filled it'.
The pairing matters in practice: df tells you a disk is full, and du -sh sorted helps you find what's eating it.
df -h # free space per file system
du -sh /var/log # total size of a directory
du -h --max-depth=1 /var | sort -rh # biggest subdirs firstKey point: 'Disk full, how do you find the culprit?' is a stock question. df to confirm, then du sorted, then look at logs and old artifacts.
A shebang is the first line of a script, like #!/bin/bash, telling the kernel which interpreter should run the file. Make the file executable with chmod +x, then run it by path, like ./hello.sh. The kernel reads the shebang and launches that interpreter with your script as its argument.
Without the executable bit you can still run it by passing it to an interpreter, like 'bash script.sh', which ignores the executable bit but still respects the interpreter you name.
#!/bin/bash
echo "Hello from $USER"
# make it runnable and execute
chmod +x hello.sh
./hello.sh
# or run without the +x bit
bash hello.shThe shell keeps a history of what you've typed. history lists it with line numbers, the up arrow steps back through it, and !! reruns the last command. Ctrl+R starts a reverse search: type a few characters and it finds the most recent matching command, which is faster than scrolling.
Handy expansions: !$ is the last argument of the previous command, and !abc reruns the most recent command starting with abc. sudo !! reruns the last command with sudo, the fix for 'permission denied'.
history | tail -20 # recent commands with numbers
!! # rerun the last command
sudo !! # rerun it with sudo
!42 # rerun command number 42 from historyKey point: Mentioning Ctrl+R reverse search indicates real terminal fluency; it's the shortcut daily users lean on and beginners rarely know.
An alias is a shorthand for a longer command: alias ll='ls -lah' means typing ll runs the long form. Defined at the prompt, an alias lasts only for that session. To keep it, put it in a shell startup file that runs when a new shell opens, like ~/.bashrc for Bash or ~/.zshrc for Zsh.
That's why 'my alias disappeared after I closed the terminal' is a startup-file question. Login shells and interactive shells read different files, which explains why an alias sometimes works in one place and not another.
alias ll='ls -lah' # for this session
alias gs='git status'
# make it permanent
echo "alias ll='ls -lah'" >> ~/.bashrc
source ~/.bashrc # reload without reopening the shellFor candidates with working experience: system internals, text processing, services, and the questions that separate users from administrators.
An inode is the data structure that stores a file's metadata: permissions, owner, group, size, timestamps, link count, and pointers to the data blocks on disk. The one thing it does not store is the filename; names live in directory entries that map to inode numbers.
That split explains hard links (many names, one inode) and why a file system can run out of inodes while showing free space: you've hit the limit on files, not bytes.
ls -i file.txt # show the inode number
stat file.txt # full inode metadata
df -i # inode usage per file systemKey point: The gotcha they hope you know: 'no space left on device' can mean inodes exhausted, not bytes. df -i is the check.
The main states are running or runnable (R), interruptible sleep (S, waiting on an event and wakeable by a signal), uninterruptible sleep (D, usually blocked on I/O and ignoring signals until it returns), stopped (T), and zombie (Z, finished but not yet reaped). ps and top show a state column with these single-letter codes.
The two that matter in interviews are D and Z. A process stuck in D is waiting on I/O and can't even be killed with SIGKILL until the I/O returns; a zombie has finished but its parent hasn't reaped its exit status yet.
| Code | State | Meaning |
|---|---|---|
| R | Running / runnable | On the CPU or ready to run |
| S | Interruptible sleep | Waiting on an event, wakes on a signal |
| D | Uninterruptible sleep | Blocked on I/O, ignores signals until done |
| Z | Zombie | Finished, waiting for the parent to reap it |
A zombie has exited but its parent hasn't called wait() to collect the exit status, so the kernel keeps a tiny entry alive. Zombies use no CPU or memory beyond that entry; a pile of them means a parent isn't reaping children. An orphan is a still-running process whose parent died.
The kernel handles orphans automatically: init (PID 1) adopts them and reaps them when they exit. Zombies clear when the parent finally reaps them or when the parent dies and init takes over.
Key point: The precise distinction (zombie is dead-but-unreaped, orphan is alive-but-parentless) is what separates a real answer here from a vague one.
systemd is the init system and service manager on most modern distributions. It runs as PID 1, brings the system up at boot, and then starts, stops, and supervises services that are defined as unit files. systemctl is the command you drive it with, and journalctl reads the logs it collects. If a service dies, systemd can restart it automatically.
The everyday verbs: start, stop, restart, status, and enable (start at boot) versus disable. A unit's status shows whether it's active, its recent log lines, and its main PID.
sudo systemctl start nginx # start now
sudo systemctl enable nginx # start on boot
systemctl status nginx # state + recent logs
sudo systemctl restart nginx # stop then start
journalctl -u nginx --since today # this service's logsKey point: Knowing enable (boot) is separate from start (now) is a common trip-up. Say both; candidates often conflate them.
cron runs commands on a schedule defined by five time fields: minute, hour, day of month, month, and day of week, followed by the command. crontab -e edits your user's schedule; crontab -l lists it. System-wide jobs live in /etc/crontab and /etc/cron.d.
The classic mistakes: cron runs with a minimal environment (so use absolute paths), and it doesn't load your shell profile, so PATH surprises are common.
# min hour day month weekday command
0 2 * * * /usr/local/bin/backup.sh # every day at 02:00
*/15 * * * * /usr/bin/health-check # every 15 minutes
0 9 * * 1 /usr/bin/report --weekly # Mondays at 09:00
crontab -e # edit your jobs
crontab -l # list themsed is a stream editor for line-based transformations: find and replace, delete lines, insert text, all in a pipeline without opening an editor. awk is a small language for column-oriented text: it splits each line into fields and lets you filter, compute, and format, which makes it the go-to for parsing logs and tabular output.
Rule of thumb: reach for sed when you're editing text and awk when you're extracting or summarizing columns.
sed 's/error/ERROR/g' app.log # replace on every line
sed -n '10,20p' file.txt # print lines 10 to 20
awk '{print $1, $4}' access.log # first and fourth column
awk -F: '{print $1}' /etc/passwd # usernames, colon-delimitedKey point: A tidy coverage names the split of labor (sed edits, awk parses columns) and shows one real one-liner for each.
You generate a key pair: a private key that stays on your machine and a public key you copy to the server's ~/.ssh/authorized_keys. When you connect, the server challenges you and your client proves it holds the matching private key, so no password crosses the wire.
It's more secure and scriptable than passwords: protect the private key with a passphrase, use ssh-agent so you type that passphrase once, and disable password login on servers once keys work.
ssh-keygen -t ed25519 -C "asha@laptop" # generate a key pair
ssh-copy-id asha@server # push the public key
ssh asha@server # connect, no password
ssh -i ~/.ssh/deploy_key asha@server # pick a specific keyip has replaced the old ifconfig for addresses and routes: ip addr shows interfaces, ip route shows the routing table. ss (the modern netstat) lists sockets and listening ports. ping tests reachability, and curl checks whether an HTTP service actually responds.
For 'why can't I reach this service', the sequence is usually: is the interface up (ip addr), is there a route (ip route), is the port listening (ss -tlnp), and does the firewall allow it.
ip addr # interfaces and IP addresses
ip route # routing table
ss -tlnp # listening TCP ports and their process
ping -c 4 example.com # reachability
curl -I https://example.com # HTTP response headersKey point: Using ip and ss (not ifconfig and netstat) signals current knowledge; the old tools are deprecated on modern distros.
ss -tlnp lists TCP listening sockets with the owning process; add a filter for the port. lsof -i :PORT does the same from the file-descriptor angle. This is the first move when a service says 'address already in use' or a port refuses connections.
sudo ss -tlnp | grep :8080 # who is listening on 8080
sudo lsof -i :8080 # same, via open files
sudo fuser 8080/tcp # PID holding the portKey point: 'Address already in use, what do you do?' The expected move is ss or lsof on the port, find the PID, then decide to stop it or change ports.
Chain small tools: extract the field you care about, sort it, collapse duplicates with a count, then sort by that count. The classic 'top N' recipe is cut or awk to pick the column, then sort, uniq -c, and sort -rn.
This composition is the heart of the Unix philosophy, and interviewers love it because it shows you think in pipes rather than reaching for a script.
awk '{print $1}' access.log | sort | uniq -c | sort -rn | headTop 10 IP addresses hitting a web log
The full one-liner: awk '{print $1}' access.log | sort | uniq -c | sort -rn | head. uniq only collapses adjacent lines, which is why sort must come first.
Key point: The load-bearing detail is why sort precedes uniq: uniq -c only counts adjacent duplicates. Volunteering that indicates real command-line experience.
xargs takes items from standard input and turns them into arguments for another command. You need it when a command takes arguments rather than stdin, like rm or chmod, and you're feeding it a list produced by find or grep.
The safe pairing is find -print0 with xargs -0, which uses null separators so filenames with spaces or newlines don't break the command.
find . -name "*.tmp" | xargs rm # delete matches
find . -name "*.log" -print0 | xargs -0 gzip # space-safe
cat urls.txt | xargs -n1 curl -sI # one URL per callAppend & to run a command in the background; jobs lists background jobs of the current shell; fg brings one to the foreground and bg resumes a stopped one. Ctrl+Z suspends the foreground job so you can push it to the background with bg.
The catch: background jobs die when the shell closes because they receive SIGHUP. Use nohup or, better, a terminal multiplexer like tmux or a systemd service for anything that must outlive the session.
long-task & # run in background
jobs # list background jobs
fg %1 # bring job 1 to foreground
nohup long-task & # survive terminal close
disown -h %1 # detach an existing job from the shelluseradd (or the friendlier adduser on Debian) creates accounts; usermod changes them; userdel removes them. Groups are how you grant shared access: create with groupadd, add a user with usermod -aG. Account data lives in /etc/passwd, groups in /etc/group, and password hashes in /etc/shadow.
The flag that catches people is -a in usermod -aG: without -a you replace the user's supplementary groups instead of appending, which quietly removes their other memberships.
sudo useradd -m -s /bin/bash asha # create with home + shell
sudo passwd asha # set password
sudo usermod -aG docker asha # append to a group
groups asha # show group membershipsKey point: The usermod -aG gotcha (forget -a and you wipe other groups) is a favorite. it is useful because signals hands-on admin work.
Linux has one directory tree, so a disk or partition becomes usable by mounting it onto a directory (a mount point). mount attaches it now; entries in /etc/fstab make mounts persist across reboots. umount detaches, and lsblk or df show what's mounted where.
The reboot-safe answer is /etc/fstab plus a mount option like defaults. A bad fstab entry can block boot, so people test with 'mount -a' before rebooting.
lsblk # block devices and mount points
sudo mount /dev/sdb1 /mnt/data # mount now
df -h /mnt/data # confirm
sudo umount /mnt/data # detach
# persist: add a line to /etc/fstab, then: sudo mount -aulimits are per-process resource caps that the kernel enforces: the maximum number of open file descriptors, the process count, memory, core dump size, and more. ulimit -n shows the open-files limit for the current shell, which is a frequent culprit when a busy server starts logging 'too many open files' under load. Raising it is often the fix.
There are soft limits (the current cap, raisable up to the hard limit) and hard limits (the ceiling, only root raises). Persistent changes go in /etc/security/limits.conf or a systemd unit's LimitNOFILE.
ulimit -n # current open-file descriptor limit
ulimit -a # all limits for this shell
ulimit -n 4096 # raise the soft limit (up to hard)
# persistent: LimitNOFILE=65535 in the systemd unittar bundles many files into one archive; on its own it doesn't compress, but it delegates to gzip, bzip2, or xz through a flag. tar -czf creates a gzip-compressed archive, tar -xzf extracts one, and tar -tzf lists the contents without extracting anything. So tar handles the grouping and the chosen tool handles the squeezing.
The letters are worth memorizing: c create, x extract, t list, z gzip, f file, v verbose. gzip alone compresses a single file; tar is what groups a directory first.
tar -czf backup.tar.gz project/ # create gzip archive
tar -xzf backup.tar.gz # extract it
tar -tzf backup.tar.gz # list without extracting
gzip large.log # compress a single filewhich shows the path of the executable the shell would run for a given name. type is more informative: it tells you whether the name is a binary, a shell builtin, an alias, or a function, which matters because an alias can shadow a real command.
command -v is the portable, scriptable version. Reaching for type when 'the command does something weird' is how you catch a sneaky alias in a dotfile.
which python3 # path to the binary
type ls # 'ls is aliased to ...' or builtin or file
command -v git # portable existence check
type -a python # every match on PATHlogrotate rotates, compresses, and deletes old logs on a schedule defined in /etc/logrotate.conf and /etc/logrotate.d. A config sets how often to rotate, how many old copies to keep, and whether to compress, so a chatty service can't quietly fill the disk.
For systemd services logging to the journal, journald has its own size and retention limits (SystemMaxUse). Naming both paths shows you know where logs actually live on a modern box.
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 14
compress
missingok
notifempty
}
sudo logrotate -f /etc/logrotate.d/myapp # force a run to testEvery command returns an exit status: 0 means success, any non-zero value means failure. The shell stores the last one in $?. Scripts read it to branch, and && / || chain commands on success or failure without an explicit if.
Conventions worth knowing: 1 is a general error, 2 is misuse, and 126/127 mean not executable or not found. In a script, 'set -e' makes it stop on the first failing command.
grep -q "ok" status.txt
echo $? # 0 if found, 1 if not
make build && ./deploy.sh # deploy only if build succeeded
ping -c1 host || echo "unreachable"
set -euo pipefail # fail fast in scriptsKey point: $? and the meaning of 0-versus-nonzero is the load-bearing idea. Bonus points for 'set -euo pipefail' as the safe script header.
Load average is the number of processes running or waiting to run, averaged over 1, 5, and 15 minutes, shown by uptime and top. On Linux it counts processes in uninterruptible sleep (D, usually disk I/O) too, so high load doesn't always mean CPU-bound.
Read it against core count: a load of 4 on a 4-core box is fully busy but not backed up; the same load on a single core means work is queueing. Compare the three windows to see whether load is rising or clearing.
uptime # load averages: 1, 5, 15 minutes
nproc # number of CPU cores to compare against
top # load line plus per-process CPUKey point: The senior nuance: high Linux load can be I/O wait, not CPU. Interpreting load against nproc, and D-state, matters.
advanced rounds probe internals, performance, and production scars. Expect every answer here to draw a follow-up.
Firmware (BIOS or UEFI) runs its power-on checks and hands off to a bootloader, usually GRUB, which loads the kernel and an initramfs into memory. The kernel initializes hardware, mounts the root file system, then starts PID 1 (systemd on most distros).
systemd brings the system to its target (multi-user or graphical) by starting units in dependency order, mounting file systems, and launching services. Naming each handoff, firmware to bootloader to kernel to init to services, is what senior the key signal is.
The Linux boot sequence
journalctl -b reads the current boot's logs; systemd-analyze blame shows which units took longest to start.
Key point: The clean coverage names all five stages in order. Dropping the initramfs step is the common gap; it's what mounts the real root.
Each process sees its own virtual address space; the kernel maps virtual pages to physical RAM on demand and tracks them in page tables. When RAM is tight, the kernel evicts less-used pages to swap space on disk, freeing RAM for active work. Swap is a safety valve, not extra fast memory: touching swapped-out pages is orders of magnitude slower.
The vm.swappiness knob tunes how readily the kernel swaps. The failure mode to name is thrashing: when the working set exceeds RAM, the system spends its time swapping pages in and out and throughput collapses.
free -h # RAM and swap usage
swapon --show # active swap devices
cat /proc/meminfo # detailed memory stats
sysctl vm.swappiness # how eagerly the kernel swapsWhen the system runs out of memory and can't reclaim enough, the kernel's out-of-memory killer picks a process to kill to keep the machine alive. It scores candidates by a badness heuristic (roughly memory footprint adjusted by oom_score_adj) and kills the highest score. You find its work in the kernel log, not the app's log.
The fixes depend on cause: cap a leaking service with cgroup memory limits, add swap as a buffer, tune oom_score_adj to protect critical processes, or right-size the box. The tell that a process was OOM-killed is a dmesg line, not a normal crash.
dmesg -T | grep -i "killed process" # was it the OOM killer?
journalctl -k | grep -i oom # kernel OOM events
cat /proc/1234/oom_score # a process's current scoreKey point: The signature clue is a dmesg/journalctl -k line, not the app log. Candidates who check the kernel ring buffer for OOM look like they've been on call.
strace traces the system calls a process makes and the signals it receives, so you see exactly where it talks to the kernel: which files it opens, which fail, which network calls hang. When a program dies silently or hangs, strace shows the last syscall it was waiting on.
The common moves: strace -f follows child processes, -e trace=file or =network filters to the calls you care about, and -p attaches to a running PID. It slows the target, so use it surgically.
strace -f ./app # trace with children
strace -e trace=open,openat ls # only file-open calls
strace -p 4821 # attach to a running PID
strace -c ./app # summary of syscall countsKey point: 'A process hangs, no useful logs, what next?' strace -p on the PID to see the blocked syscall is the answer that indicates production-grade.
Namespaces isolate what a process can see: separate PID trees, network stacks, mount tables, users, and hostnames, so a process thinks it has its own system. cgroups (control groups) limit and account for what a process can use: CPU, memory, I/O, and process count.
Together they are the kernel foundation of containers. Docker and Kubernetes don't invent isolation; they orchestrate namespaces for the 'separate view' and cgroups for the 'capped resources'. Saying that split is the answer to 'how do containers actually work?'.
| Mechanism | Provides | Example |
|---|---|---|
| Namespaces | Isolation (separate view) | Own PID tree, network, mounts |
| cgroups | Limits and accounting | Cap a container to 512 MB and 1 CPU |
| Together | Containers | Docker, Podman, Kubernetes pods |
Key point: The crisp mental model: namespaces are what you see, cgroups are what you can use. Reciting that split is the production signal on any container question.
A signal is an asynchronous notification the kernel delivers to a process. Most can be caught and handled; two cannot: SIGKILL (9) and SIGSTOP force termination and suspension with no handler. Handlers let a program shut down gracefully, reload config, or reopen log files.
The production set: SIGTERM (polite shutdown, the default kill and what orchestrators send), SIGHUP (traditionally 'reload your config'), SIGKILL (last-resort force), and SIGINT (Ctrl+C). Graceful shutdown means catching SIGTERM to drain connections before exiting.
| Signal | Number | Typical meaning |
|---|---|---|
| SIGTERM | 15 | Please shut down cleanly (default kill) |
| SIGKILL | 9 | Force kill, cannot be caught |
| SIGHUP | 1 | Reload configuration (by convention) |
| SIGINT | 2 | Interrupt from keyboard (Ctrl+C) |
Key point: Graceful shutdown, catching SIGTERM to drain before exit, is what orchestration questions are really probing. the signal maps to the behavior.
Confirm and locate first: top or htop to see whether one process or many, and whether it's user CPU, system CPU, or I/O wait. If it's one process, drill in with per-thread view (top -H) or a profiler like perf top to find the hot function. If it's I/O wait masquerading as load, iostat and iotop point at the disk.
Then correlate with recent deploys and traffic, form a hypothesis (a hot loop, a retry storm, GC pressure, a runaway query), and verify with a measurement before you change anything. The structure, observe then narrow then verify, is what the question scores.
top -H # per-thread CPU inside a process
perf top # live view of hot kernel/user functions
pidstat 1 # per-process CPU over time
iostat -x 1 # is it actually I/O wait?Key point: in disguise. the technical evaluation checks observe, hypothesize, verify, fix, confirm; tools support the evidence, not the point.
The usual cause is a deleted file that a process still holds open. The directory entry is gone so du can't see it, but the inode and its blocks stay allocated until the last file descriptor closes. A rotated log file that the app still writes to is the classic culprit.
Find it with lsof | grep deleted, then either restart the holding process or truncate the file through its /proc file descriptor. The other suspect is inode exhaustion, which df -i confirms.
df -h # space is gone
du -sh /* 2>/dev/null # but this does not account for it
sudo lsof | grep deleted # deleted-but-open files
df -i # or inodes are exhaustedKey point: The deleted-but-held-open file is the answer they want. Candidates who reach for 'lsof | grep deleted' have clearly hit this in real life.
The kernel's netfilter framework hooks packets at points in their journey, and a firewall tool writes rules into it. iptables organizes rules into tables (filter, nat, mangle) and chains (INPUT, OUTPUT, FORWARD); each packet walks the relevant chain and the first matching rule decides accept, drop, or reject. nftables is the modern replacement with one unified tool and better performance.
In practice many teams use a front end (ufw or firewalld) that generates the low-level rules. Knowing the chain model underneath, and that nftables is where new work goes, is the level this question checks.
sudo iptables -L -n -v # list filter rules with counts
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo nft list ruleset # nftables equivalent
sudo ufw status # if a front end manages itStatic linking copies library code into the executable at build time, so it runs with no external dependencies but is larger and can't pick up a library security fix without rebuilding. Dynamic linking leaves the executable referencing shared libraries (.so files) that load at runtime, so binaries are smaller and share one copy of a library, patched in one place.
The trade-off is portability versus size and updatability. ldd shows a binary's shared-library dependencies; a missing .so is the classic 'error while loading shared libraries' failure.
ldd /usr/bin/curl # shared libraries it needs
file /usr/bin/curl # dynamically vs statically linked
ldconfig -p | grep libssl # what the linker cache knowsBoth are virtual file systems backed by the kernel, not disk. /proc exposes per-process and system information as files: /proc/PID/ has a process's memory maps, open files, and status; /proc/meminfo and /proc/cpuinfo report system state. /sys exposes device and kernel object information and lets you tune some kernel parameters by writing to files.
This is why so many Linux tools are just formatted reads of /proc, and why 'everything is a file' is more than a slogan. sysctl is the friendly front end for the tunable knobs under /proc/sys.
cat /proc/cpuinfo # CPU details
cat /proc/1234/status # a process's state and memory
cat /proc/loadavg # raw load average
sysctl net.ipv4.ip_forward # a tunable kernel parameterKernel modules are pieces of kernel code (device drivers, file systems, network protocols) that load and unload at runtime instead of being compiled in, so you don't rebuild the kernel to add a driver. lsmod lists loaded modules, modprobe loads one and its dependencies, and rmmod removes it.
modprobe is preferred over the older insmod because it resolves dependencies and reads config from /etc/modprobe.d. Modules run in kernel space, so a buggy one can crash the whole system, which is why signed modules matter on locked-down hosts.
lsmod # loaded modules
sudo modprobe nvme # load a module and dependencies
modinfo nvme # details about a module
sudo rmmod some_module # unloadext4 is the reliable general-purpose default: mature, stable, good for most workloads. XFS handles large files and high parallel throughput well, which is why it's common on big data and RHEL servers. Btrfs and ZFS add copy-on-write features: snapshots, checksums, and built-in volume management, at the cost of more complexity and memory.
The interview answer is matching the file system to the workload rather than declaring one best: ext4 for general use, XFS for large-file throughput, a copy-on-write file system when you want cheap snapshots and integrity checking.
| File system | Strength | Good fit |
|---|---|---|
| ext4 | Mature, stable, simple | General-purpose default |
| XFS | Large files, high parallel I/O | Big data, RHEL servers |
| Btrfs / ZFS | Snapshots, checksums, CoW | Integrity, cheap snapshots |
For normal tasks the kernel uses a fair scheduler that tracks how much CPU time each runnable task has received and picks the one that's fallen furthest behind, so no task starves. Priority is expressed with the nice value, from -20 (highest) to 19 (lowest), which biases how much CPU a task gets.
Beyond that, real-time scheduling policies (SCHED_FIFO, SCHED_RR) let latency-sensitive work preempt normal tasks, and cgroups can hand a whole group a CPU share. renice adjusts a running process's niceness; ionice does the analogous thing for disk I/O priority.
nice -n 10 ./batch-job # start lower priority
renice -n 5 -p 4821 # change a running process
chrt -f -p 50 4821 # set a real-time policy
ionice -c3 -p 4821 # idle disk I/O priorityLayer the defenses: SSH keys only with password login disabled and root login off, a firewall allowing only needed ports, automatic security updates, and least-privilege accounts with sudo instead of shared root. Add a fail2ban-style ban on brute-force attempts and keep the attack surface small by removing services you don't run.
Then the deeper layer: mandatory access control (SELinux or AppArmor) confining services, audited logging shipped off-box, integrity monitoring, and secrets kept out of the repo and the environment where you can. The production-ready answer frames it as defense in depth, not one silver setting.
Key point: Framing it as defense in depth, several layers, no single fix, is what indicates senior. A list of one-off tweaks indicates junior.
Both are mandatory access control systems that confine what a process can do even when Unix permissions would allow more, so a compromised web server can't read files outside its policy. SELinux labels every file and process and enforces rules against those labels; it's strict and ships enabled on Red Hat distros. AppArmor confines programs by file path, which is simpler to author, and it's the default on Ubuntu.
The trade-off is granularity versus approachability. SELinux is more precise and harder to author; AppArmor is easier to reason about but path-based, so a renamed binary can slip its profile. The pragmatic coverage names both and picks per distro rather than declaring a winner.
| SELinux | AppArmor | |
|---|---|---|
| Model | Labels on files and processes | Paths to executables |
| Granularity | Very fine, strict | Coarser, easier to read |
| Default on | RHEL, Fedora, CentOS | Ubuntu, Debian, SUSE |
| Effort to author | Higher | Lower |
Key point: Naming both and tying each to its distro (SELinux on RHEL, AppArmor on Ubuntu) indicates someone who's actually run confined services, not just read about MAC.
Linux wins when you need transparency, low cost, and control: it's free, open source, scriptable end to end, and runs the same on a laptop and a fleet of servers. It trades polished consumer defaults and some commercial software support for that openness, which is why it owns servers and cloud while staying a minority on desktops. Windows leads on desktop software and enterprise directory tooling; macOS gives a Unix shell on tightly integrated hardware. Knowing where each fits, and saying it plainly, signals you pick tools on merits.
| Linux | Windows | macOS | |
|---|---|---|---|
| License | Open source, mostly free | Proprietary, paid | Proprietary, tied to Apple hardware |
| Kernel | Linux (monolithic, modular) | Windows NT (hybrid) | XNU (hybrid, BSD + Mach) |
| Shell native | Bash, Zsh, POSIX sh | PowerShell, cmd | Zsh, Bash (Unix shell) |
| Dominant use | Servers, cloud, Android, embedded | Desktop, enterprise directory | Developer laptops, creative work |
| Package model | apt, dnf, pacman, and others | MSI, winget, Store | Homebrew, App Store |
Prepare in layers, and practice in a real terminal. Most Linux rounds move from concept questions to live command work to a troubleshooting scenario, so rehearse each stage rather than only reading answers.
The typical Linux interview flow
Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.
10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.
Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Linux questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.
See how the AI Coding Interviewer works