How to teach software engineering (not just computer science)
Most students learn how to make programs work. Software engineering is learning how to keep systems working when requirements change, users do unexpected things, dependencies fail, the “simple” fix breaks something else, and nobody on the team remembers why the code is like this.
That gap isn’t about intelligence. It’s about feedback loops and operational context.
In school, feedback is usually immediate: compile, run, unit test, grade.
In real systems, feedback is delayed and messy: a graph trends the wrong way, an alert fires at 2am, a customer says “it’s slow,” and the logs don’t say why.
What theory teaches well
Theory gives students tools that matter:
- how to reason about correctness
- how to model complexity and tradeoffs
- how to design clean interfaces
- how to communicate an idea precisely
Keep that. Don’t throw it away.
But “correct” is only one axis in production.
What production adds (that students rarely get graded on)
Here are the most common “I can code, but I can’t ship” failure modes:
1) Ownership beats brilliance
The hard part isn’t writing the first version. The hard part is:
- knowing who is responsible for a system
- knowing where to look when it breaks
- making the next person faster, not slower
This is mostly documentation, interfaces, and habits.
2) Failure is normal
Real systems fail constantly in small ways:
- timeouts
- retries and duplicates
- partial outages
- stale caches
- “it worked yesterday”
If students only ever build things that succeed, they never learn to design for failure.
3) Debugging is a first-class skill
Debugging isn’t “print statements.” It’s:
- forming hypotheses
- narrowing the search space
- using logs/metrics/traces to confirm or reject a theory
- writing down what you learned so the same incident doesn’t repeat
4) Changes need safe rollout
Most production pain comes from change:
- a deploy that breaks a critical path
- a migration that locks a table
- a configuration change nobody can reproduce
Teaching students to ship includes teaching them how to undo a change safely.
A small set of practical skills worth teaching
You don’t need a full DevOps course. You can teach software engineering by adding a few constraints to normal assignments:
- Observability: require structured logs and a basic “health check” endpoint.
- Runbooks: require a one-page “what to do when it breaks” doc.
- Failure injection: intentionally break a dependency (timeouts, 500s) and make students diagnose it.
- Change management: require a migration plan and a rollback plan.
- Ownership: require an “owner” and an escalation path for each component.
These map directly to how teams operate in industry, and they’re teachable.
Assignment template: ship, break, fix, write
If you want one repeatable assignment pattern that forces practical learning:
- Students build a small service (or feature) with a clear contract.
- You provide a “production-ish” harness:
- a flaky dependency
- a little load
- a few canned failure modes
- Students add:
- structured logs (request id, endpoint, status)
- at least one metric (requests/sec or error rate)
- a runbook that includes “first checks”
- You run an “incident”:
- something degrades
- they triage, mitigate, and explain
- They submit:
- a short postmortem (what happened, why, what they changed, how they’ll prevent repeats)
- the runbook updates that would have made the incident faster
Grade them on clarity and recovery, not just code output.
If you’re teaching this semester
If you want students to graduate with job-ready instincts, make “running the system” part of the curriculum.
That’s the difference between knowing the right answer and being able to ship the right outcome with a team.
We’re piloting a faculty program with a small cohort of engineering programs: two semesters free.
Apply here: Faculty program.