Software engineering project ideas: 12 builds that practice the job, not the syntax
Software engineering projects practice what happens around the code: testing, review, delivery, design and running a service. Good ones are a refactor with characterization tests, a mutation tester, a CI pipeline, a feature flag service, a zero-downtime migration, an architecture rules checker, an SLO error budget and a written incident postmortem. Below are 12 projects in five groups, each with a stack, what it teaches, a first version, a stretch goal and a time estimate.
What is a software engineering project, as opposed to a coding project?
A coding project proves you can make a program work once. A software engineering project proves you can keep it working while it changes, other people touch it and real traffic hits it. The deliverable is less the code than the evidence around it: tests that fail when they should, a pipeline that deploys without hands, a document that explains why, a graph that shows when it is broken.
Most of these projects need a subject: a small service to test, deploy and watch. Reuse one across several projects. A notes API with three endpoints and a database table is enough, or the ledger from full stack project ideas. If you have never built that first service, start with coding project ideas. If you are choosing a project for a degree, capstone project ideas covers the format, and computer science project ideas covers the algorithmic side.
| Skill employers ask about | Project | Group |
|---|---|---|
| Working with legacy code | Refactor under characterization tests | Quality |
| Judging test quality | Mutation tester | Quality |
| Service boundaries | Contract tests between two services | Quality |
| Continuous integration | CI pipeline from scratch | Delivery |
| Safe releases | Feature flag service | Delivery |
| Database changes with no downtime | Expand and contract migration | Delivery |
| Communicating design | Design doc before code | Design |
| Keeping architecture intact | Import boundary checker | Design |
| Seeing what a system does | Logs, metrics and traces | Operations |
| Reliability targets | SLO and error budget report | Operations |
| Learning from failure | Incident drill with postmortem | Operations |
| Reading unfamiliar code | Code ownership report from git | Operations |
Which projects teach testing and quality?
Tests are code that protects other code. These three each ask a different question: can I change code I do not understand, are my tests any good, and do two teams agree on what they send each other?
01 Refactor under characterization tests
Take messy code with no tests, pin down what it does today with tests, then clean it up and add a feature without breaking anything.
- Stack
- Any language with a test runner.
- Teaches
- Characterization testing, safe small refactors, naming, extracting functions, working with code you did not write.
- First version
- Fork the Gilded Rose kata. Write tests that record current behavior for every item type. Commit. Refactor in steps of under 10 minutes, running tests after each.
- Stretch goal
- Add the requested “Conjured” item rule, then write up each refactoring step as a commit message that says why.
- Time
- 6 to 10 hours.
A characterization test records what the code does, even if that looks wrong. You are not judging, you are fencing the behavior in. Generate inputs systematically: every item type crossed with quality 0, 1, 49, 50 and sell-in -1, 0, 1. The commit history is the deliverable: it should read as a sequence of small, safe steps.
02 Mutation tester
A tool that changes your code in small ways, such as flipping a plus to a minus, and reports which changes your tests failed to notice.
- Stack
- Any language; start with a small expression language or one module.
- Teaches
- What mutation testing measures, syntax trees or text transforms, running tests as a subprocess, test quality versus coverage.
- First version
- Mutate five operators (+ to -, == to !=, < to <=, true to false, return value to zero) in one file. Run the tests for each mutant. Print a report of survivors.
- Stretch goal
- Only mutate lines changed in the last commit, run mutants in parallel, and produce an HTML report.
- Time
- 10 to 18 hours.
A surviving mutant means the tests would still pass if that code were wrong. Run your tool on a module with 100 percent line coverage and see how many mutants survive: coverage tells you a line ran, not that anything checked it. A mature tool such as Stryker is useful to compare your results with.
Write a small mutation testing tool in Python for a single target file. Input: path to a source file and a test command, for example `pytest tests/test_calc.py`. Mutations (one per run, one location at a time): + to -, == to !=, < to <=, True to False, and each return value replaced with 0. Process: for each mutation, write the mutated file, run the test command, restore the original, record killed (tests failed) or survived (tests passed). Output: a table of line, original, mutated, result, and a final score killed/total. Do not build: parallelism, HTML reports, support for other languages. Done when: running it on calc.py with a deliberately weak test file reports survivors, and with the full test file reports none.
03 Contract tests between two services
Two small services where the consumer states what it needs from the provider, and a test fails the provider's build when a change would break it.
- Stack
- Two small HTTP services, Pact or a hand-written contract file.
- Teaches
- Consumer-driven contracts, API compatibility, versioning, testing without a shared environment.
- First version
- An orders service calls a customers service. The consumer writes a contract: GET /customers/42 returns id and email. The provider's test replays it.
- Stretch goal
- Rename a response field on purpose, see the provider build fail, then do the rename in a compatible two-step instead.
- Time
- 10 to 16 hours.
Write the first contract by hand as a JSON file before you use a tool. You will then see what the tool automates. The point is that the failure shows up in the provider's pipeline, before release, with a message that names the consumer.
Which projects teach delivery and releases?
Delivery is how code gets from a laptop to users. These three cover the pipeline, the switch that controls exposure, and the hardest thing to ship: a change to the database.
01 CI pipeline from scratch
A pipeline that checks every pull request for formatting, types, tests and build, and deploys the main branch with no hands.
- Stack
- GitHub Actions or another CI service, your subject service.
- Teaches
- Workflow files, caching, required checks, secrets handling, preview deploys, rollbacks.
- First version
- On pull request: lint, type check, unit tests, build. Merge is blocked until all pass. On merge to main: deploy to a staging URL.
- Stretch goal
- Cache dependencies and bring a 6-minute run under 3, add a manual approval before production, and a one-command rollback.
- Time
- 6 to 10 hours.
Record the run time before and after each speed-up. Break the build on purpose in a pull request to confirm that it is blocked. Keep secrets in the CI service’s secret store and check that they never appear in logs.
02 Feature flag service
A small service that decides, per user, whether a feature is on, with percentage rollouts that stay stable as you increase them.
- Stack
- Any language, a small database or a JSON file.
- Teaches
- Feature toggles, stable hashing, targeting rules, caching, kill switches, flag cleanup.
- First version
- A flag has a key, an on/off value and a rollout percentage. Evaluate by hashing the user id with the flag key into a bucket from 0 to 99.
- Stretch goal
- Targeting by attribute, an audit log of changes, and a report of flags that have been 100 percent on for 30 days and should be deleted.
- Time
- 10 to 16 hours.
Worked example: 2,000 users and a 10 percent rollout puts roughly 200 users in the new code path. Raise it to 20 percent and the same 200 stay in, plus about 200 more, because each user’s bucket never changes. Write a test for exactly that. Random assignment on each request would flip users back and forth, which is the bug this design prevents.
03 Expand and contract database migration
Rename a column in a live table without downtime, in steps where every intermediate version of the app still works.
- Stack
- Postgres or MySQL, your subject service, a load script.
- Teaches
- Parallel change, backfills, dual writes, locks, deploy ordering.
- First version
- Rename customers.name to full_name across five steps while a script keeps calling the API and counts errors. The count stays zero.
- Stretch goal
- Backfill 1,000,000 rows in batches of 1,000 with a pause between batches, and a rollback plan for each step.
- Time
- 8 to 14 hours.
- Expand: add the new column full_name, nullable.
- Write both: the app writes name and full_name on every change.
- Backfill: copy name into full_name for old rows, in batches.
- Read new: switch reads to full_name once every row has it.
- Contract: stop writing name, then drop the column in a later release.
A one-step rename breaks any server still running the old code during a deploy. The five steps never do. Run the load script through every step and keep the error count on screen.
Which projects teach design and architecture?
Design is the part of engineering that happens before and between the code. These two make it visible: one in writing, one as a check that runs on every commit.
01 Write the design doc first
Pick a feature, write a short design document with options and trade-offs, get a reader to challenge it, then build it and record what changed.
- Stack
- A text editor, your subject service.
- Teaches
- Requirements, non-goals, comparing options, architecture decision records, review.
- First version
- A two-page design for “add rate limiting to the API” with three options compared, plus one ADR for the choice. Another person comments. Then you implement it.
- Stretch goal
- After building, write a short “what the design got wrong” note and update the doc.
- Time
- 8 to 12 hours.
The easiest part to skip is non-goals, and it is the part readers value most. Be explicit about what the feature will not do. The note on what the design got wrong is the most useful part of the repository a year later.
Title: Author / reviewers / date: Status: draft | in review | accepted | superseded 1. Problem (what hurts, for whom, with one real example) 2. Goals (3 bullets, measurable if possible) 3. Non-goals (3 bullets: what this will not do) 4. Options considered (A, B, C: one paragraph each, with cost and risk) 5. Recommendation and why (reference the goals) 6. Rollout and rollback (how it ships, how we undo it) 7. Open questions (who must answer, by when) ADR (one per decision): Context: ... Decision: We will ... Consequences: easier ... harder ...
02 Import boundary checker
A tool that reads your codebase and fails the build when a module imports something its layer must not depend on.
- Stack
- Any language; for JavaScript, compare with dependency-cruiser.
- Teaches
- Layered architecture, dependency graphs, static analysis, rules as code, cycles.
- First version
- A rules file such as “ui may import domain, domain may not import ui or db”. Parse imports with a regex or syntax tree. Print each violation with file and line, exit 1 if any.
- Stretch goal
- Cycle detection, a graph picture of modules, and a baseline file so existing violations are tolerated while new ones fail.
- Time
- 8 to 14 hours.
Run it on a project that has drifted and count the violations. The baseline file is what makes it adoptable: a checker that fails on day one, on code nobody can fix this week, gets switched off. A checker that only blocks new violations stays on.
Which projects teach running and understanding a service?
The last four are about what happens once the code is live, and about reading code you did not write. These are the skills that separate someone who has shipped from someone who has not.
01 Logs, metrics and traces for a two-service app
Add structured logs, a metrics endpoint and distributed traces to two services that call each other, and answer “why was this request slow?” from the data.
- Stack
- Two small services, OpenTelemetry, a local trace viewer.
- Teaches
- The three signals, correlation ids, context propagation, sampling, what to alert on.
- First version
- JSON logs with a request id on every line, a latency histogram per route, and a trace that spans both services. Add a sleep to one dependency and find it in the trace.
- Stretch goal
- A dashboard with request rate, error rate and p95 latency, and an alert rule that fires when errors pass 2 percent for 5 minutes.
- Time
- 10 to 16 hours.
The test of success is a question, not a graph. Inject three faults, such as a slow query, a failing dependency and a retry storm, and check that you can tell which is which from the data alone, without reading the code.
02 SLO and error budget report
Pick a reliability target for your service, compute from request logs how much of the budget you have used, and report it.
- Stack
- Request logs or metrics from the project above, a script, a chart.
- Teaches
- Service level indicators and objectives, error budgets, burn rate, Google’s SRE guidance.
- First version
- SLO: 99.9 percent of requests succeed in a 30-day window. A script reads logs and prints requests, failures, budget and budget left.
- Stretch goal
- A burn-rate alert that warns when the budget would run out in two days at the current rate.
- Time
- 6 to 10 hours.
| Target | Allowed downtime | Per 1,000,000 requests |
|---|---|---|
| 99% | 432 minutes (7.2 hours) | 10,000 failures |
| 99.9% | 43.2 minutes | 1,000 failures |
| 99.95% | 21.6 minutes | 500 failures |
Compute the table yourself before reading it. Then decide what counts as a failure for your service: an error code, a response slower than 500 milliseconds, or both. That decision is the real work.
03 Incident drill with a written postmortem
Break your own service on purpose, find the cause using only your monitoring, then write a blameless postmortem.
- Stack
- Your deployed subject service and its monitoring.
- Teaches
- Diagnosis under uncertainty, timelines, root cause versus trigger, follow-up actions, postmortem culture.
- First version
- Have a friend change one thing in a copy of the service: a config value, a dependency version or a database index. Find it with your tools. Write the postmortem.
- Stretch goal
- Run it as a timed 45-minute drill, and turn each action item into a ticket or an automated check.
- Time
- 6 to 10 hours.
A bad config change is a common trigger, and the board has an idea about exactly that: which env var changed between the deploy that worked and the one that didn’t. Write down what was hard to see, because that is your next monitoring task.
Title and date: Impact: who was affected, for how long, how badly (numbers) Timeline (UTC): detection, first action, mitigation, resolution Trigger (what changed): Root cause (why the system allowed it): What went well: What was hard to see or slow: Action items (owner, due date, one of: prevent, detect, mitigate):
04 Code ownership report from git history
A script that reads a repository's history and shows which files only one person has ever changed, and which files change the most.
- Stack
- Any scripting language, git log output.
- Teaches
- Reading history, bus factor, churn, hotspots, parsing command output.
- First version
- For the last 12 months, list files with a single author and files with the most commits. Print the top 20 of each.
- Stretch goal
- Weight by lines changed, merge authors who use two emails, and flag files that are both high churn and single-author.
- Time
- 4 to 8 hours.
Use git log with a custom format and the numstat option so you get one line per file per commit, then group in code. Run it on a large open source project you want to contribute to. The files where one person did everything are where a newcomer is most likely to break something, or most needed. The board’s Which of your abandoned repos is closest to finished is a similar history-reading idea.
Where can I find something real to engineer?
Engineering practice sticks when something is at stake. The board at Post your build ideas collects ideas that people will not build themselves. Browse all ideas or the developer tools category, pick one, and build it with the practices above: a design doc, a pipeline, a flag, an error budget. Every idea has a generated build prompt for Claude Code, Cursor or Lovable. Posted does not mean validated, so ask one person who has the problem before you spend a month on it.
If you build with an AI tool, the practices here matter more, not less. The tool writes code quickly; tests, boundaries and monitoring are how you find out whether it is right. How to write an app build prompt covers the prompt side.
Frequently asked questions
What is a good software engineering project for students?
Refactor the Gilded Rose kata under characterization tests, then build a CI pipeline for it. Both are small, need no new product idea, and show skills employers ask about: working with existing code and automating checks. Write the evidence into the README.
How is a software engineering project different from a coding project?
A coding project proves a program can work once. A software engineering project shows it can keep working as it changes: tests that catch regressions, a pipeline that ships it, a design record that explains choices, and monitoring that shows when it breaks. The artifacts around the code are the deliverable.
Which software engineering projects look best on a résumé?
Ones with visible evidence: a pipeline run, a dashboard, a design doc, a postmortem. Say what question the project answered and what you measured, such as build time cut from 6 minutes to 3. Hiring reviewers can check those claims in the repository quickly.
Do I need a team for these projects?
No, but a reviewer helps. Ask a friend to challenge your design doc, to break your service for the incident drill or to review a pull request. One honest reader makes the project resemble real work much more than any extra feature would.
How many hours do these projects take?
Most take 6 to 16 hours for a first version that works, on top of a subject service that already exists. The migration, pipeline and ownership report are the shortest. The mutation tester and observability projects are the longest. Do one each weekend rather than all at once.
Can an AI coding tool help with software engineering projects?
Yes. It can write a pipeline file, a flag evaluator or a log parser quickly. The judgement is yours: decide the rules first, ask for tests that prove them, and read what it wrote. Characterization tests are especially useful before asking a tool to refactor.
Created with AI assistance and edited for this site. Examples are illustrative and do not represent customer research. About the publisher.