π Things CS Degrees Don't Teach You
Eight practical engineering skills that every CS graduate has to learn on the job β from Git workflows to debugging, testing, operations, and writing for humans.
π― The Gap Between School and Reality
A computer science degree teaches you beautiful things: Turing completeness, NP-completeness, the halting problem, Big-O notation, red-black trees, cache coherence protocols.
What it doesnβt teach you is how to ship software.
Not because CS theory is irrelevant β itβs foundational β but because the daily work of engineering lives in a different skill set: reading error messages, navigating legacy code, debugging race conditions, deploying without downtime, and writing PR descriptions that colleagues can actually understand.
Here are eight gaps every CS graduate encounters in their first year on the job.
1οΈβ£ Git Workflows
CS programs treat Git as a magical git push button. In practice, Git is a distributed DAG manipulation tool, and your universityβs βcommit β push β doneβ workflow is dangerously incomplete.
What They Donβt Teach
| Concept | Why It Matters |
|---|---|
| Branching strategies | Git flow, trunk-based development, GitHub Flow β each fits different teams |
| Rebase vs merge | Merge preserves history; rebase rewrites it. When to use each is a team decision |
| Interactive rebase | git rebase -i for squashing, reordering, fixing commit messages |
| Cherry-picking | Applying specific commits across branches |
| Bisecting | git bisect to find the commit that introduced a regression |
| Stashing | git stash for context switching without losing work |
| The reflog | git reflog β the safety net that saves you after a bad reset |
The One-Liner That Changes Everything
git rebase -i main
This is the single most important Git command for keeping a clean history. Before you merge, you should be able to look at your branch and see one logical commit per change, with a clear message explaining why.
Code Review Etiquette
| Do | Donβt |
|---|---|
| Write descriptive PR titles and summaries | Leave βFixed bugβ or empty descriptions |
| Keep PRs small (<400 lines) | Create 5,000-line PRs that take days to review |
| Respond to comments within 24 hours | Ignore feedback for weeks |
| Explain why in commit messages | Only describe what changed |
| Use draft PRs for early feedback | Wait until βperfectβ to share |
| Approve with optional nits clearly labeled | Block a PR over formatting preferences |
2οΈβ£ Debugging
University teaches you to write code. It doesnβt teach you to read code thatβs broken, which is where youβll spend most of your professional time.
Using a Debugger
Most CS graduates have never used a debugger. They use print() statements. While print-debugging works for simple cases, it fails for:
- Multi-threaded race conditions (prints reorder, timing changes behavior)
- Segfaults (canβt print after memory corruption)
- Signal handlers and interrupts
- Embedded or remote systems where
stdoutisnβt available
# GDB: gold standard for C/C++
gdb ./my_program
(gdb) break main
(gdb) run
(gdb) print variable_name
(gdb) backtrace
(gdb) frame 3
# PDB: Python's built-in debugger
import pdb; pdb.set_trace() # python < 3.7
breakpoint() # python >= 3.7
# LLDB: modern C/C++/Swift debugger
lldb ./my_program
(lldb) breakpoint set --name main
(lldb) run
(lldb) frame variable
(lldb) thread backtrace
Reading Stack Traces
A stack trace is a gift. It tells you exactly where the program was when it broke. The top of the trace is the crash site; the bottom is the entry point.
Traceback (most recent call last):
File "app.py", line 45, in <module> β entry point
result = process_data(input_data)
File "processor.py", line 123, in process_data
transformed = transform(item) β call site
File "transformer.py", line 67, in transform
return apply_rules(data['value']) β crash site (KeyError: 'value')
KeyError: 'value'
Each frame tells you: the file, the line number, the function, and the local state. Read from bottom to top: the crash is at the bottom, the caller chain is above it.
Bisecting Regressions
When something that used to work stops working, git bisect runs a binary search through your commit history to find the exact commit that broke it:
git bisect start
git bisect bad # current commit is broken
git bisect good v1.0 # v1.0 was working
# Git checks out a commit halfway between
# Run your test, mark good or bad
# Repeat ~log2(N) times
git bisect reset # return to original state
For a 500-commit range, bisect finds the culprit in ~9 steps. Most engineers donβt know this exists and waste hours manually scrolling through git log.
3οΈβ£ Reading Other Peopleβs Code
In university, you write your own code from scratch. In industry, you read 10x more code than you write.
Navigating Large Codebases
How to approach a codebase you've never seen:
1. README first β does a setup guide exist?
2. Entry point β where does the program start? (main, app.py, index.js)
3. Tests β tests are executable documentation
4. Commit history β `git log --oneline` shows what changed and why
5. Architecture docs β if they exist, read them before any source file
6. One feature end-to-end β trace a single request through the system
7. Don't understand everything β 80% understanding of 20% of the codebase is enough
The Rubber Duck
Explain the code youβre reading to an inanimate object. If you canβt explain a function to a rubber duck, you donβt understand it well enough to modify it.
4οΈβ£ Testing
University teaches you to write code that works on your machine with your inputs. Industry requires code that works on every machine with every input.
The Testing Pyramid
β±ββββββ²
β± E2E β² β Fewer tests, higher confidence
β±ββββββββββ²
β± Integration β² β Service-level tests
β±ββββββββββββββββββ²
β± Unit Tests β² β Many tests, fast feedback
β±ββββββββββββββββββββββββ²
| Test Type | Speed | Confidence | What It Catches |
|---|---|---|---|
| Unit | <1ms | Low per-test, high in aggregate | Logic errors, edge cases |
| Integration | 10β500ms | Medium | API contract breaks, DB issues |
| E2E | 1β30s | High | Pipeline failures, configuration drift |
Mocking
Mocking isolates the code under test from its dependencies (databases, APIs, file systems). The skill is knowing what to mock and what to keep real:
- Mock external APIs (you donβt control them)
- Mock I/O (files, sockets, network calls)
- Do NOT mock your own code (test the real thing)
- Do NOT mock trivial dependencies (plain data transformations)
Testing in CI
A test that only passes on your machine doesnβt exist. Every test must run in CI. Every CI failure must block the merge. These are non-negotiable rules in professional engineering that are never taught in school.
5οΈβ£ Operations
βThe computer worksβ is an assumption at university. In industry, itβs a hypothesis you verify continuously.
Deployment
Local dev β Feature branch β CI β Staging β Production β Monitoring
Each step catches a different class of error:
| Stage | Catches |
|---|---|
| Local dev | Syntax errors, basic logic |
| Feature branch | Merge conflicts, test failures |
| CI | Compilation errors, lint, regressions |
| Staging | Configuration drift, environment differences |
| Production | Real user traffic, scale issues |
| Monitoring | Latency regressions, error rate increases |
On-Call
You will be woken up at 3 AM. Prep for it:
- Runbooks: Document every alert and its response. If youβre fixing the same thing twice, automate it.
- Escalation paths: Know who to call when you canβt fix it.
- Postmortems: Every incident gets a written postmortem. Blameless. What broke, how we fixed it, how we prevent it next time.
SLOs / SLIs / SLAs
| Term | Definition | Example |
|---|---|---|
| SLI (Service Level Indicator) | What you measure | Latency p99, error rate |
| SLO (Service Level Objective) | The target | 99.9% of requests <200ms |
| SLA (Service Level Agreement) | The promise (with consequences) | 99.95% uptime, or users get credits |
The goal is to measure what matters, target achievable reliability, and tradeoff reliability for velocity intentionally.
6οΈβ£ Writing for Humans
The most important engineering skill nobody teaches: clear writing.
Code Review Comments
Bad: "This is wrong" β Unhelpful, demotivating
Bad: "Fix this" β No context, no explanation
Good: "What happens if `user` is null here? We dereference it on line 42 without checking."
Good: "Consider extracting this validation into a helper β it's repeated in 3 places."
RFCs / Design Docs
A good design doc answers five questions:
- What problem are we solving? (Context)
- Why now? (Motivation)
- What approaches did we consider? (Options)
- What are we doing and why? (Decision)
- What are the risks? (Trade-offs)
Documentation
Write docs for your future self, who has forgotten everything about this system. A year from now, youβll be grateful you left a comment explaining why, not just what.
7οΈβ£ Trade-Off Thinking
CS teaches you that thereβs a βcorrectβ answer. Real engineering teaches you that βit dependsβ is the correct answer to most questions.
| Question | CS Answer | Engineering Answer |
|---|---|---|
| Which database? | Relational vs NoSQL | It depends on consistency needs, scale, team expertise, infra |
| Monolith vs microservices? | Microservices | It depends on team size, deploy velocity, organizational structure |
| Which language? | Pick the best one | It depends on ecosystem, team skills, existing codebase |
| Test coverage target? | 100% | It depends on risk tolerance, code criticality |
The Halting Problem of Engineering Decisions
There is no algorithm to determine the βrightβ architecture. Every decision is a trade-off between:
Time-to-market β Code quality
Developer speed β Operational simplicity
Feature count β System reliability
Recognizing when youβre optimizing the wrong variable is the mark of a senior engineer.
8οΈβ£ Reading Error Messages
This sounds trivial. Itβs not. Most errors tell you exactly whatβs wrong, but engineers panic, scroll past the error, and start guessing.
How to Read an Error
1. Read the FIRST line β What type of error? (KeyError, NullPointer, etc.)
2. Read the LAST line β The actual message (often contains the answer)
3. Read the stack trace β Where did it happen? (file, line number)
4. Read the error line β What was the program doing?
5. Read the context β What values were in play?
Example
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "calculator.py", line 12, in add
return a + b
The error tells you:
- Type:
TypeError(wrong type used) - Operation:
+failed - Types involved:
intandstr(canβt add a string to an integer) - Location:
calculator.py:12inadd - Variables:
aisint,bisstr
The fix is likely at the call site β maybe a str was passed where an int was expected, or b needs to be converted with int(b).
Most errors contain the answer if you read them carefully enough. The skill is staying calm and reading the full output before reaching for Stack Overflow.
π‘ Key Takeaways
- Git is a DAG manipulation tool, not a βsaveβ button β learn branching, rebasing, bisecting
- Debuggers are rocket fuel for problem solving β learn at least one (gdb, lldb, pdb)
- Reading code is 10x more important than writing it β navigational skills are learned, not taught
- Testing is a career, not a checkbox β the pyramid, mocking, and CI integration are all essential
- Operations is the forgotten layer β deployment, on-call, SLOs are half of engineering
- Writing is thinking β clear PRs, RFCs, and comments separate great engineers from good ones
- βIt dependsβ is a professional answer β trade-off reasoning is the core skill at senior+ levels
- Error messages tell you the answer β read them before guessing
CS degrees give you the theory. Real engineering is what you build on top of it.