---
title: 'User Stories and Tasks'
source: 'https://academia.sh/en/courses/user-experience/user-stories-and-tasks'
course: 'User Experience and Behavior Design'
language: en
updated: '2026-08-19T05:19:59+00:00'
license: 'CC BY-SA 4.0'
---

# User Stories and Tasks

Turning a need into a story and a story into a task; checking for solution bias in a story, and ranking the task inventory by frequency, failure rate, and failed attempts.

The previous lesson produced three personas: Known-Record Searcher, Subject Browser, and
Narrow-Screen Viewer. Personas say who is coming, not what they do. The Subject Browser
persona's session lasts twenty-one and a half minutes; what steps happen in that time,
which step requires which other step, and which one should the design prioritize?

This lesson turns the need into action. It first establishes the distinction among need,
story, and task; then it checks how a solution seeps into a story; and finally it ranks
the task inventory drawn from observation data by three separate criteria, and shows that
these rankings do not agree with each other.

## Need, Story, and Task

The three concepts are often used interchangeably, and wherever they are confused, the
design decision becomes blurred.

**A need** is independent of the interface. "Knowing what has been written on a subject"
is a need; it exists without the catalog interface too, and it can also be met by asking
a librarian.

**A user story** ties the need to a role and a goal. Its format is short and carries
three parts: who, what, why. A story does not propose a solution on its own; it narrows
the solution space.

**A task** is the observable counterpart of a story: a sequence of actions with a start,
an end, and a success criterion. "Browse by subject" is a task; it starts, it ends, and
whether it counts as successful can be measured. Tasks break down into subtasks: within
browsing by subject there are steps such as "narrow the results," "open a record," and
"return to the list."

The chain is one-directional. It descends from need to story, from story to task, from
task to interface decision. If walked in reverse — if an interface element is thought of
first and then a story is written to justify it — research data does not produce the
decision; it decorates it.

## Solution Bias in Stories

The most common way a story breaks down is when an interface element gets written in
place of the need. The sentence "I want a publication-year filter" looks like a need but
is a solution; the need is "telling recent sources apart," and a filter is not its only
counterpart. The check below scans stories at the level of format, role, and wording.

```js
// story.mjs — checking user stories for format and solution bias

const STORIES = [
  "As an undergraduate student, I want a publication-year filter on search results, so that I can tell recent sources apart",
  "As a subject browser, I want to return to my result list after opening a record, so that I can continue comparing",
  "As a faculty member, I want to select a language from a dropdown menu",
  "As a library staff member, I want to see whether a record is on the shelf, so that I can tell the reader whether the book is available",
  "As a user, I want a better search screen, so that I can search more conveniently",
  "As an external member, I want to extend my borrowing period, so that I can keep working without returning the book",
];

// Format: As a <role>, I want <need>, so that <goal>
const FORMAT = /^As an? (.+?), I want (.+?), so that (.+)$/;
// Solution vocabulary: words naming an interface element
const SOLUTION = ["filter", "button", "dropdown menu", "tab", "checkbox", "screen", "page", "form", "slider"];
// Generic names that do not stand in for a role
const GENERIC_ROLE = ["user", "person", "everyone", "people"];

console.log("no  format      role                    finding");
STORIES.forEach((s, i) => {
  const m = s.match(FORMAT);
  const findings = [];
  if (!m) findings.push("format incomplete (goal not written)");
  const role = m ? m[1] : (s.match(/^As an? ([^,]+)/)?.[1] ?? "");
  if (GENERIC_ROLE.includes(role.toLowerCase())) findings.push("role generic");
  const leaked = SOLUTION.filter((c) => s.toLowerCase().includes(c));
  if (leaked.length) findings.push(`solution bias: ${leaked.join(", ")}`);
  console.log(
    `${String(i + 1).padStart(2)}  ${(m ? "complete" : "incomplete").padEnd(10)}  ${role.slice(0, 22).padEnd(22)}  ${findings.length ? findings.join(" | ") : "-"}`
  );
});

const clean = STORIES.filter((s) => {
  const m = s.match(FORMAT);
  const role = m ? m[1] : "";
  return m && !GENERIC_ROLE.includes(role.toLowerCase()) &&
    !SOLUTION.some((c) => s.toLowerCase().includes(c));
});
console.log(`\nstories passing the check: ${clean.length} / ${STORIES.length}`);
```

```
no  format      role                    finding
 1  complete    undergraduate student   solution bias: filter
 2  complete    subject browser         -
 3  incomplete  faculty member          format incomplete (goal not written) | solution bias: dropdown menu
 4  complete    library staff member    -
 5  complete    user                    role generic | solution bias: screen
 6  complete    external member         -

stories passing the check: 3 / 6
```

Three of the six stories pass the check. The check is not a measure of correctness but a
review tool: because it relies on a word list, it does not catch a solution outside the
list, and it can flag an innocent usage by mistake. Its function is to move the
discussion from the level of wording to the level of structure.

Each of the three findings shows a different flaw. In the first story, the goal is
written correctly, but a solution has taken the need's place; its corrected form becomes
"being able to see publications in order of recency," and this leaves multiple solutions
open — filter, sort, date range. In the third story, there is no goal at all: because why
the language selection is needed is never written, the story cannot be tested. In the
fifth story, the role is "user" — which of the three personas this is remains unclear,
and "more conveniently" cannot be measured.

## Task Inventory and Priority

Once stories are turned into tasks, the question becomes which one to take on first.
During the observation period, the attempt and success counts were logged for each task.

```js
// task.mjs — ranking the task inventory by frequency, failure rate, and failed attempts

// Task attempts logged during a four-week observation period (data constructed for this lesson)
const TASKS = [
  { name: "find a known record",   attempts: 148, successes: 132 },
  { name: "find the shelf location", attempts: 121, successes: 79 },
  { name: "borrow",                attempts: 94,  successes: 88 },
  { name: "browse by subject",     attempts: 86,  successes: 51 },
  { name: "renew a loan",          attempts: 37,  successes: 19 },
  { name: "reserve",               attempts: 22,  successes: 12 },
  { name: "add to reading list",   attempts: 14,  successes: 13 },
];

for (const t of TASKS) {
  t.failed = t.attempts - t.successes;
  t.failureRate = t.failed / t.attempts;
}

console.log("task                     attempts  successes  failed  failure rate");
for (const t of TASKS) {
  console.log(
    `${t.name.padEnd(24)} ${String(t.attempts).padStart(8)} ${String(t.successes).padStart(10)} ${String(t.failed).padStart(7)}  ${(t.failureRate * 100).toFixed(1)}%`
  );
}

const rankedNames = (key) =>
  [...TASKS].sort((a, b) => b[key] - a[key]).map((t) => t.name);
const RANKINGS = {
  "frequency (attempts)": rankedNames("attempts"),
  "failure rate": rankedNames("failureRate"),
  "failed attempts": rankedNames("failed"),
};

console.log("\nrank  frequency                 failure rate              failed attempts");
for (let i = 0; i < TASKS.length; i++) {
  console.log(
    `${String(i + 1).padStart(4)}  ${RANKINGS["frequency (attempts)"][i].padEnd(25)} ` +
      `${RANKINGS["failure rate"][i].padEnd(25)} ${RANKINGS["failed attempts"][i]}`
  );
}

// The rank of the same task across the three rankings
console.log("\ntask                     frequency  rate  failed  largest rank gap");
let largest = { name: "", gap: -1 };
for (const t of TASKS) {
  const r = Object.values(RANKINGS).map((l) => l.indexOf(t.name) + 1);
  const gap = Math.max(...r) - Math.min(...r);
  if (gap > largest.gap) largest = { name: t.name, gap };
  console.log(`${t.name.padEnd(24)} ${String(r[0]).padStart(9)} ${String(r[1]).padStart(5)} ${String(r[2]).padStart(7)}  ${gap}`);
}
console.log(`\ntask with the largest rank shift: ${largest.name} (${largest.gap} ranks)`);
console.log(`total failed attempts: ${TASKS.reduce((t, x) => t + x.failed, 0)}`);
const topTwo = [...TASKS].sort((a, b) => b.failed - a.failed).slice(0, 2);
console.log(
  `${((topTwo.reduce((t, x) => t + x.failed, 0) / TASKS.reduce((t, x) => t + x.failed, 0)) * 100).toFixed(1)}% of failed attempts are in two tasks: ` +
    topTwo.map((t) => t.name).join(", ")
);
```

```
task                     attempts  successes  failed  failure rate
find a known record           148        132      16  10.8%
find the shelf location       121         79      42  34.7%
borrow                         94         88       6  6.4%
browse by subject              86         51      35  40.7%
renew a loan                   37         19      18  48.6%
reserve                        22         12      10  45.5%
add to reading list            14         13       1  7.1%

rank  frequency                 failure rate              failed attempts
   1  find a known record       renew a loan              find the shelf location
   2  find the shelf location   reserve                   browse by subject
   3  borrow                    browse by subject         renew a loan
   4  browse by subject         find the shelf location   find a known record
   5  renew a loan              find a known record       reserve
   6  reserve                   add to reading list       borrow
   7  add to reading list       borrow                    add to reading list

task                     frequency  rate  failed  largest rank gap
find a known record              1     5       4  4
find the shelf location          2     4       1  3
borrow                           3     7       6  4
browse by subject                4     3       2  2
renew a loan                     5     1       3  4
reserve                          6     2       5  4
add to reading list              7     6       7  1

task with the largest rank shift: find a known record (4 ranks)
total failed attempts: 128
60.2% of failed attempts are in two tasks: find the shelf location, browse by subject
```

The three rankings produce three separate lists, and no task sits at the same rank in
all three.

**By frequency**, "find a known record" ranks first; it is the most attempted task. But
its failure rate is 10.8%, and only sixteen of the hundred and forty-eight attempts are
failures. Frequency alone does not set design priority; a task that is done a lot but
works well is not an area for improvement.

**By failure rate**, "renew a loan" ranks first: 48.6%. This rate correctly describes the
severity of the problem but not its size; the task was attempted thirty-seven times and
produced eighteen failures.

**By failed attempts**, "find the shelf location" ranks first: forty-two failures. This
task is second by frequency, fourth by failure rate — first in neither list, but it
produces the largest share of the total damage. Of the total hundred and twenty-eight
failed attempts, 60.2% are concentrated in two tasks.

Priority ranking is built not by picking one of the three but by defining what the
problem is. If the goal is to reduce total damage, the failed-attempts ranking is used.
If the goal is to raise a task's reliability above a threshold, the failure rate is used.
Frequency alone is not a priority criterion; it is a multiplier for the other two: a high
rate on a rarely attempted task can produce fewer failures than a low rate on a
frequently attempted one.

A caveat is needed: these numbers count only tasks that were **attempted**. A task the
user wanted to do but could not find the way to, and so never attempted, does not enter
the inventory. The fourteen attempts at "add to reading list" may show not that this task
is rare, but that it is rare to be **found**. This is the inventory's blind spot, and it
can only be closed by an interview.

## The Ethical Limit of Task Data

The task inventory is produced from per-person session logs; this is the most identifying
form of research data. The three rules apply here too. A session log is collected from
**consenting** participants; logging everyone on a publicly open interface is not
research but surveillance. The data that enters analysis is **aggregated**: the table
above has no people in it, only tasks. Per-person records are deleted within the
**retention limit** once aggregation is done; only the numbers remain.

A related design rule follows from this: every new field collected to enrich the
inventory opens one more path back to identity. Knowing whether a task succeeded does not
require knowing who attempted it.

## Acceptance Criteria Make a Story Testable

Even a story that passes the check is not enough on its own; when it is satisfied must be
written down. **Acceptance criteria** are a story's verifiable counterpart, and they are
written in terms of the task.

The second story — "being able to return to my result list after opening a record" — is
tied to these criteria: on return, the search term, the applied refinements, and the
position in the list are preserved; a record the user has already opened stays marked in
the list; the return does not require re-running the search. All three are observable;
all three are testable.

The solution is again kept out while writing the criterion. "A back button is added" is
not a criterion but a decision; a criterion states what the result must be, regardless of
which button achieves it.

## Summary

- A need is independent of the interface, a story ties the need to a role and a goal, and
  a task is the story's observable counterpart with a start, an end, and a success
  criterion.
- Solution bias is the most common way a story breaks down; the wording and format check
  is a review tool, not a measure of correctness, and it flagged three of the six sample
  stories.
- The same task inventory produces three separate rankings by frequency, failure rate,
  and failed attempts; in the sample data, one task shifted by four ranks across the
  three rankings.
- The priority criterion is derived from the definition of the problem: failed attempts
  is used for total damage, failure rate for reliability; frequency alone does not set
  priority.
- The inventory counts only attempted tasks; a task that is never attempted is invisible,
  and this blind spot can only be closed by an interview.
- A session log is collected from consenting participants, enters analysis in aggregated
  form, and per-person records are deleted within the retention limit.

## Next Step

The task inventory shows each task as a separate row, yet the user performs them back to
back: they find a record, check the shelf location, go to the shelf, cannot find the
book, and return to the catalog. The task producing the largest share of failed
attempts, "find the shelf location," is a step that starts on screen and ends off screen.
The next lesson traces this chain end to end: it shows what columns a journey map
consists of, why stage duration and drop-off points do not sit in the same place, and how
off-screen steps enter the map.
