Three Perspectives on the Same Problem

When we sat down to write this post, we realized we each had completely different mental models for why labs fail.

Sarah (product): Learners drop off because the goal isn't clear. They don't know what "done" looks like, so they lose momentum.

Alex (engineering): Labs fail when the environment isn't stable. One flaky dependency and the whole experience collapses, and the learner blames themselves instead of the setup.

Priya (developer advocacy): Learners quit when they feel dumb. The content assumes too much context. The error messages don't explain anything. The learner feels like an outsider.

All three of us are right. And fixing any one problem without fixing the others doesn't move the needle much.

This post is our attempt to synthesize those three perspectives into a framework you can use when building your own labs.


Part 1: Start with the Exit Condition (Sarah)

Before writing a single step, write the success state.

Not the learning objectives. Those are backward-looking. The success state is a concrete, observable thing the learner can point to when they're done.

Bad: "Learners will understand Docker networking."

Good: "By the end of this lab, you will have two containers talking to each other over a custom bridge network, and you'll be able to explain why exposing port 5432 to the host is unnecessary."

The difference isn't just clarity. It's testability. A concrete success state can be validated automatically. Vague learning objectives cannot.

Once you have the exit condition, work backward:

  1. What is the last thing the learner does?
  2. What does that require them to know?
  3. What does that require?

This reverse-mapping exposes what you actually need to teach. It almost always reveals that your original scope was too broad by half.

Rule of thumb: If a lab takes more than 45 minutes, it should be two labs.


Part 2: Build Environments Learners Can Trust (Alex)

The most demoralizing experience in a hands-on lab is typing the exact command from the instructions and getting an error.

Not because you did something wrong. Because something in the environment is different from what the author tested against.

Here's what we do at Stepwik to prevent this:

Pin everything

Every base image, every package version, every CLI tool. If your lab uses aws-cli, don't install aws-cli. Install aws-cli==2.15.14. Five months from now, the unversioned install will pull something different and break step 7 for a reason no one can immediately diagnose.

Smoke-test the environment at lab start

Before the learner runs a single command, run a silent verification that the environment is healthy:

# .stepwik/preflight.sh
node --version || exit 1
docker info > /dev/null 2>&1 || exit 1
aws --version || exit 1

If this fails, surface a clear error before the learner wastes 20 minutes debugging the wrong thing.

Use reset points

Long labs should have checkpoints where the learner can reset to a known-good state. This isn't admitting failure. It's good UX. Stuck on step 9 of 15? Here's a button that fast-forwards to a working step 8.

This also dramatically lowers the cost of making a mistake. When mistakes are cheap, learners experiment more. That's how learning actually happens.

Test the lab end-to-end before publishing

Not just reviewing the markdown. Actually going through every step in a fresh environment. Every time. This is the discipline that separates good labs from great ones.


Part 3: Write for the Person Who Doesn't Know What They Don't Know (Priya)

Most technical content is written for someone who almost already knows the thing. The author can't help it. They've internalized so much context that they've forgotten what it was like not to know it.

The result is content that works for the top quartile of your target audience and confuses everyone else.

Here's the mindset shift that helped me: write for the person who is about to quit.

They're on step 4. Something isn't working. They're not sure if they did something wrong or if the instructions are wrong. They're deciding whether to push through or close the tab.

What does that person need?

They need to know what "working" looks like. Show expected output. Not just "run this command" but "you should see something like this." A screenshot or a code block with sample output costs almost nothing and prevents enormous frustration.

They need errors to be explained. If running your command commonly produces a specific error, address it proactively. "If you see permission denied, run chmod +x first. This is expected on Linux."

They need to know why, not just what. "Run this command" teaches nothing. "Run this command, which tells Docker to create a new named volume that will persist even if the container restarts" teaches something.

The extra sentence almost always doubles the educational value of the step.


Putting It Together: A Pre-Publishing Checklist

Before you publish any lab, run through this:

  • Is the exit condition concrete and testable?
  • Have all dependencies been pinned to explicit versions?
  • Does the environment have a preflight check?
  • Is expected output shown for every non-trivial command?
  • Are the three most common errors explained proactively?
  • Has someone who doesn't know the material walked through it end-to-end?
  • Are there reset points for labs longer than 30 minutes?
  • Does every step explain why, not just what?

If you can check all eight boxes, you're probably publishing something good.


The Meta-Point

Good labs are hard to write. They require more care than blog posts, more precision than documentation, and more empathy than most technical content ever asks of its authors.

But the payoff is real. A lab that works the first time, in the hands of a confused learner at 11pm, is the best onboarding, the best tutorial, and the best documentation you'll ever write.

We build the tools. You bring the knowledge. Let's build something together.