A familiar idea can make a strong portfolio. Give it real constraints, make it fail, and show
how you brought it back.
The interesting part starts after the happy path: a worker dies, a request arrives twice, one
user tries to read another user’s data. What did you decide? What evidence says the decision
holds?
The premise
Depth is a set of decisions you can defend.
A clone, a shop, or a dashboard can contain serious engineering. A novel idea can still stop
at a polished interface. Choose a small product you care about, then follow its
responsibilities all the way through.
Use AI to help build it if you like. Keep ownership of the reasoning: explain the code,
challenge its assumptions, reproduce its failures, and describe what you would change.
Beginner projects / A smaller starting point
Practice the work.
One responsibility. A clear finish line. Room to go deeper.
Choose the work you want to get better at. Follow either path independently, finish its first
exercise, and investigate one failure before adding more. Each next step extends something you
already understand. Open an exercise for its scope, finish line, and failure check.
Backend 01 → 02
Own a business rule.
Start with a small module you can test. Let storage, audit, and useful boundaries grow from a working behavior.
Before you startFunctions, basic data types, and running a test in your language. Go is a good fit; the exercise does not depend on it.
01Start hereOrganization membershipIdentity, permissions, and explicit errors+
Build this much
Seed one organization, a fixed owner, and a few existing users. Write functions to add a member, change a member between viewer and editor, and list members. Only the owner can make changes. Begin with in-memory storage and pass known actors from a test harness.
Your finish line
Tests show an owner adding and updating a member, a viewer being denied, and a duplicate membership being rejected. Each rejection leaves the original state intact and returns an error the caller can distinguish.
Make one thing fail
Ask an actor from another organization to change a known member ID. Predict the result, run it, and prove that knowing an ID does not grant permission.
Apply a concept
Compare two error designs against the same tests. Can a caller recognize forbidden, duplicate, and missing-member outcomes without inspecting message text?
Scope & optional extensions
Keep owner transfer, sign-up, and invitation email outside this first slice. If you expose an HTTP API later, derive the actor from an authenticated session; the fixture actor is only a test input.
02Next stepAudited role changesTransactions, failure visibility, and boundaries+
Build this much
Extend the membership module with a relational database and an initial migration. Store each successful role change and its audit entry in one transaction: actor, organization, member, previous role, new role, and operation ID.
Your finish line
Restart and find both the accepted role and its history. Reject the audit write during a role change: neither write commits. An unauthorized attempt creates no successful-change audit entry.
Make one thing fail
Inject a failure between the role update and audit insertion. Trace the operation through structured logs, then inspect the database. The evidence must explain both the failure and the rollback.
Apply a concept
Audit answers who changed what. Operational logs explain how execution went. Keep secrets out of both, and preserve useful error causes without exposing database details to API callers.
Scope & optional extensions
Add a read-only history query, then propagate context across an HTTP request and a queued job. Document what identifies the request, what groups the workflow, and which operation caused the job. Add a second adapter only when it serves a concrete test or integration.
Translate a visual reference into a usable screen. Then keep that screen understandable when data and timing change.
Before you startBasic HTML and CSS, plus components and local state in one framework. Use the framework and styling tools you want to practice.
01Start hereOne design, one complete screenVisual fidelity, responsive layout, and accessibility+
Build this much
Choose a design you may reuse, such as a Figma Community settings or members screen, and credit its author. Implement its type, spacing, colors, and a few reusable controls. Use local fixture data and make one form work with local state.
Your finish line
Compare the implementation with the reference. Check a narrow screen, a wide screen, and widths between them. Long names fit, controls have labels, keyboard focus is visible, and submitting invalid input gives a useful message.
Make one thing fail
Replace the neat sample content with a very long name and an empty list, then complete the form using only the keyboard. Record the layout or interaction that broke and the fix.
Apply a concept
Use component composition to share behavior and design decisions. Check whether each component boundary makes the second screen easier to build and change.
Scope & optional extensions
Add a second screen that shares the controls, then change a spacing or color token. Explain what should change together and what belongs to one screen. A complete design system can grow from repeated needs.
02Next stepA screen that handles uncertaintyAsync state, stale responses, and recovery+
Build this much
Connect the screen to a controllable mock API. Add member search with loading, results, empty, and failed states. Let the mock delay, reject, or reorder requests. Give a failed read an explicit retry action.
Your finish line
Search for A, then B, and return A last. The screen still represents B. An empty response looks different from a failed request; retry succeeds; state changes remain understandable to keyboard and screen reader users.
Make one thing fail
Return search results in the wrong order, then fail the current request. Explain which response may update the screen and show a recovery path that keeps the current search input.
Apply a concept
Model the states and allowed transitions explicitly. A small API boundary lets the same screen use controlled fixtures and a real service later; the mock does not prove server authorization.
Scope & optional extensions
Add a role-edit form with pending, validation-error, and successful states. Preserve input after failure. Decide how a lost response is reconciled before retrying a mutation; a disabled button alone does not prevent duplicate server effects.
These larger briefs are projects to grow into. Start with one product loop, then build toward
the full acceptance checks in stages. Each capability has a job in the product.
Give a developer an endpoint, show what arrives in real time, and let them forward or
replay a captured request. Then make the destination slow, broken, or unreliable.
The depth comes naturally: durable intake, tenant isolation, background delivery,
backpressure, and a receiver that might finish the work before its connection drops.
Includes eight chaos drills and a lost-response demo script.
Full project briefGradual rollout / uneven propagation
Feature flag service
Let a team release a feature to a stable group of users, increase its reach, and roll back
a bad change. Build the small SDK that makes the decision inside a demo application.
The depth lives between publication and adoption: consistent user assignments, stale
snapshots, concurrent edits, and an application instance that misses a rollback.
Includes eight chaos drills and a two-instance rollback demo script.
Full project briefOffline work / competing edits
Offline inspections
Complete an equipment checklist, take notes, and attach photos without reception.
Reconnect and bring the work back, even when another device edited the same inspection.
The depth sits between local save and server acceptance: durable intent, interrupted
uploads, explicit conflicts, and permissions or schemas that changed while a device was
away.
Includes eight chaos drills and a two-device conflict demo script.
Brief sketch / Messy input. Partial progress.
Bulk import & repair
Let a team upload a data file, preview validation errors, import accepted rows, and repair rejected ones.
›Explore this direction
First useful slice
One file format, one record type, a preview, a durable import job, and a row-level results screen.
Where the depth comes from
Give each workspace its own imports and permissions. Checkpoint batches, enforce upload and storage quotas, and give each row a stable identity for retries. Migrate existing imports when the validation schema changes.
Break it on purpose
Kill the worker after a batch commits but before its checkpoint updates. Also submit a malformed row and the same file twice.
Bring back evidence
A rerun completes without duplicating accepted records. Show row errors, import lag, an alert for stalled work, and a trace from upload to batch.
Brief sketch / Time moves. Dependencies fail.
Scheduled report delivery
Let a team schedule a report, inspect its runs, and deliver the result to a controlled inbox.
›Explore this direction
First useful slice
One report, one schedule per workspace, a test inbox, and a history of completed and failed runs.
Where the depth comes from
Separate who can view reports from who can change schedules. Persist each scheduled occurrence, cap work per tenant, and distinguish retrying a delivery from creating another run. Evolve schedules through a migration with old runs intact.
Break it on purpose
Restart the scheduler at a due time, return 500 from the inbox, and change the schedule while a run is queued.
Bring back evidence
Show one run identity per scheduled occurrence, bounded delivery attempts, and a documented missed-run policy. Alert on lateness and trace one report through delivery.
Brief sketch / Large files. Limited capacity.
Asset processing pipeline
Let a workspace upload images, generate a few variants in the background, and inspect processing progress.
›Explore this direction
First useful slice
One supported image format, two variant sizes, authenticated uploads, and a durable processing history.
Where the depth comes from
Authorize originals and variants, persist job state, cap file size and worker concurrency, and reuse a stable variant identity on retry. Version the transform recipe and migrate existing metadata without losing originals.
Break it on purpose
Upload a corrupt file, stop a worker after a variant is written, and fill the processing queue.
Bring back evidence
Show that retries do not create duplicate variants, corrupt inputs fail clearly, and other workspaces keep making progress. Include queue-age alerts and a trace for a failed transform.
The engineering bar
Make each capability earn its place.
The full project briefs include authentication, authorization, persistence, background jobs,
retries, idempotency, migrations, rate limiting, observability, and monitoring. The beginner
exercises introduce a smaller set of responsibilities. Add each capability when the work gives
it a purpose, and include a way to notice when its promises stop holding.
Build them in stages. An installed package or a checked box is the beginning; a repeatable
demonstration is the evidence.
01
Authentication
Who is making this request?
›What to demonstrate
Sign in, expire a session, revoke a credential, and show what access stops.
02
Authorization
What are they allowed to touch?
›What to demonstrate
Use two accounts. Try reading and changing an object owned by the other one.
03
Persistence
What survives a restart?
›What to demonstrate
Restart the app after it accepts work. Find the same data and explain the durability boundary.
04
Background jobs
Who owns work after the request ends?
›What to demonstrate
Stop a worker mid-job. Restart it and show how the unfinished work is found.
05
Retries
Which failures deserve another attempt?
›What to demonstrate
Make a dependency fail. Show the delay, attempt budget, and final outcome.
06
Idempotency
What if the same operation arrives twice?
›What to demonstrate
Send duplicates concurrently. Show the stored operation and the downstream effect count.
07
Migrations
How does yesterday’s data meet today’s code?
›What to demonstrate
Upgrade a populated database, verify old records, and rehearse the recovery plan.
08
Rate limiting
What happens when one caller sends too much?
›What to demonstrate
Exceed a quota. Explain what is rejected and how another caller remains usable.
09
Observability
Can you explain one failed operation?
›What to demonstrate
Connect its request, job, and attempts using structured logs and traces without exposing secrets.
10
Monitoring
How would you know users are stuck?
›What to demonstrate
Trigger a defined alert, use its runbook, and show the signal returning to normal.
Build → break → explain
Give the project a chaos mode.
A reviewer should be able to choose a slow endpoint, a 500 response, a dropped connection, or
malformed input. Make the failure repeatable. Show the affected state, the signal that exposed
it, and the recovery path.
Show the product working, inject one fault, inspect the evidence, and recover. Let the viewer follow one event the whole way.
02
An architecture with promises
Mark the acceptance boundary, the job handoff, and the systems you control. State what survives a crash and where duplicate effects remain possible.
03
A measured workload
Publish the test command, environment, duration, limits, and observed results. Explain what the measurement does and does not establish.
04
A small incident report
Record the symptom, signal, diagnosis, mitigation, and prevention. Include a tradeoff you would revisit with more time or traffic.
05
A reproducible handoff
Provide local setup, synthetic fixtures, a migration rehearsal, and the fault reset procedure. Explain decisions and code you can defend, including work assisted by AI.