Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

UI/UX Design  Module 3 – From research to insight

Synthesis: affinity mapping and themes

Turn interview notes into themes and insights with affinity mapping, keep every insight traceable to evidence, and check AI-assisted clustering.

  • Intermediate
  • 15 minutes
  • Examples run with Node.js 24.21.0, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Group qualitative notes into themes using an affinity diagram
  • Distinguish observations, insights and implications
  • Explain why every insight must cite notes from at least two participants
  • Apply counts correctly in qualitative research and check AI-assisted clustering

Before you start

What you’ll learn · Before you start

What you will learn

  • Group qualitative notes into themes using an affinity diagram
  • Distinguish observations, insights and implications
  • Explain why every insight must cite notes from at least two participants
  • Apply counts correctly in qualitative research and check AI-assisted clustering

Before you start

On this page

You have 24 notes from eight MealBox interviews about why people cancel in their first week. Nobody has looked at them yet: quotes on sticky notes, observations in a spreadsheet, time-stamped lines in a document. By the end of this lesson you turn them into five insights that cite real quotes, with implications a product lead can act on.

MealBox (mealbox.example) is the lunch-subscription service in Pune that this track invents; the .example domain is reserved by RFC 2606, so nothing here points at a real business. The eight fictional participants, their quotes and every number below are invented for this lesson.

Thematic analysis: the method behind affinity mapping

thematic analysis: A method for working through qualitative notes in six phases: familiarise, code, search for themes, review against the notes, define themes and write up. is a method for working through qualitative notes. Braun and Clarke’s 2006 paper in Qualitative Research in Psychology names six phases: familiarise yourself with the data, generate initial codes, search for themes, review themes against the notes, define and name themes, and write up. The phases loop: a theme that splits sends the researcher back to the codes, and a code that does not fit reopens the notes.

An affinity diagram: A visual method of grouping notes into clusters on a wall or board, where themes emerge from the notes themselves rather than from a pre-written codebook. is the visual, bottom-up version of the same loop. Notes go on a wall or a digital board, codes cluster into groups, and the groups become themes. Bottom-up means the themes come from the notes, not from a codebook written before the research — the inductive approach. A codebook-first approach is deductive, and a hybrid starts with a codebook but adds codes as the notes demand.

Step through the five stages of one MealBox synthesis and see what each stage leaves behind. One theme splits when it reaches the notes again:

Interactive · step by step From 24 notes to 5 insights Step through each stage and see what it leaves behind. One theme goes back to its notes and splits in two.
From 24 notes to 5 insights, step 6 of 6. Showing Notes, Codes (3 codes, 24 notes), Themes (3 themes, 1 split), Insights (5 cited insights), Implications (3 owned actions). In focus: Implications.codegroupwriteactNotes24 notes, 8 peopleCodes3 codes, 24 notesThemes3 themes, 1 splitInsights5 cited insightsImplications3 owned actions

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Raw notes from eight interviews. Eight MealBox subscribers who cancelled in their first week were interviewed. The team has 24 notes: quotes, observations and moments of pause or confusion, each with a participant id. Nothing is interpreted yet.
  2. Coding each note. Each note gets a short label that says what it is about: travel-conflict, no-pause or money-unclear. A note that touches two patterns can carry two codes.
  3. Grouping codes into themes. Codes cluster into candidate themes. travel-conflict and no-pause point to one root: subscribers cannot skip a week, so any trip forces a cancellation. money-unclear starts as a second theme.
  4. A theme goes back to its notes. Reviewing the money theme against its six notes shows two kinds of note: two people only did not know what happens to their balance, four chose cancel over pause because of it. The theme splits into "balance feels lost" and "money blocks pause".
  5. Writing insights as findings. Each theme yields one or two sentences that state what was found, such as "People cancel because they cannot pause, not because they dislike the food." Every insight cites notes from at least two participants.
  6. Acting on the implications. Each insight leads to something the product lead can act on: add a pause option, name what happens to the balance, and show the pause before the cancel. The loop ends when every action has an owner.

Key idea

Thematic analysis is not a single pass: codes emerge from notes, themes emerge from codes, and a theme that fails to hold the notes splits or merges.

Checkpoint

Check: Thematic analysis and affinity mapping

  1. Question 1 of 2 Braun and Clarke name six phases of thematic analysis. Which phase comes immediately after coding?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Searching for themes

    The six phases are familiarisation, coding, searching for themes, reviewing themes, defining and naming themes, and writing up. Each phase feeds the next, and the loop goes back when a theme splits or fails.

  2. Question 2 of 2 An affinity diagram is the visual, bottom-up version of thematic analysis. What does "bottom-up" mean here?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The themes come from the notes, not from a codebook written before the research

    Bottom-up and inductive mean the same thing here: the codes are not decided before the notes arrive. Top-down and deductive use a pre-written codebook.

Observation, insight and implication

Three words that sound similar but mean different things:

  • An observation states what happened to the sample: “4 of 6 participants paused at the PIN code field.”
  • An insight (UX): A sentence that interprets a pattern in research data and states what it means, actionable because it says something a product lead can respond to. interprets the pattern and says what it means: “People only trust delivery after they see their area is served.”
  • An implication says what to do about it: “Check the PIN code before plan choice.”

Write insights as sentences with a subject and a verb, not as topics. “Pricing” is a topic; “People pay more willingly when they can see their delivery day” is an insight. The difference matters because a topic does not tell anyone what to do.

The MealBox synthesis ended with five insights, each built from the codes and backed by the notes. One example:

Row The pause insight, written out
Insight People cancel because they cannot pause, not because they dislike the food.
Evidence 10 notes from 8 participants coded no-pause.
Implication Add a pause option and surface it before the cancel button.

Writing themes as topics instead of claims

Wrong: a board full of sticky notes under single-word labels like “Pricing”, “Delivery”, “Trust”. Right: the same board where every label is a sentence that says what was found. A label that is not a sentence is a category, not a theme.

Checkpoint

Check: Observation, insight and implication

  1. Question 1 of 2 "4 of 6 participants paused at the PIN code field." Is that an observation, an insight or an implication?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: An observation

    An observation is a fact about the sample. An insight interprets it (people only trust delivery after they see their area is served). An implication says what to do (check the PIN code before plan choice).

  2. Question 2 of 2 Which of these is written as an insight rather than a topic?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: "People pay more willingly when they can see their delivery day."

    An insight is a sentence that someone outside the research could act on. "Pricing" names a category but says nothing about what was found.

Keeping insights traceable

evidence-insight-implication: A three-part way of writing up a research finding: the evidence (what was observed), the insight (what it means) and the implication (what to do about it). means every insight carries a link to the evidence that supports it:

  • Every note has a participant id (P1, P2, …) and, when the session was recorded, a timestamp.
  • Every code links to the notes that carry it.
  • Every insight cites notes from at least two participants, because a single person’s note might be an outlier.

The chain runs both ways. A product lead who doubts an insight can follow it back to the notes. A researcher who finds a new pattern can trace it forward to see which insights it supports.

The program below holds the 24 notes and 5 insights from the MealBox synthesis. It checks that every note is coded, that every insight cites notes from at least two participants, and prints what passes and what fails:

Validate traceability: every note coded, every insight backed by 2+ participants Python · affinity-check/affinity_check.py
"""Validate a synthesis for traceability: every note coded, every insight backed by notes from ≥ 2 participants.

All data is invented for this lesson. MealBox (mealbox.example) is a fictional lunch-subscription
service; the participant ids P1–P8 and every quote are original to this track.
"""

# Eight fictional participants from MealBox cancellation interviews, 24 notes total
# Each note has a participant id, a short quote or observation, and a code (theme label)
NOTES = [
    {"id": "n01", "participant": "P1", "text": "I travel for work a lot, so a fixed weekly plan is pointless.", "code": "travel-conflict"},
    {"id": "n02", "participant": "P1", "text": "I wanted to pause for one week, but there was no option.", "code": "no-pause"},
    {"id": "n03", "participant": "P2", "text": "The first meal arrived while I was at a client site.", "code": "travel-conflict"},
    {"id": "n04", "participant": "P2", "text": "I searched everywhere for a skip button.", "code": "no-pause"},
    {"id": "n05", "participant": "P2", "text": "I have no idea what happens to the money if I pause.", "code": "money-unclear"},
    {"id": "n06", "participant": "P3", "text": "My parents came to visit and I ate at home.", "code": "travel-conflict"},
    {"id": "n07", "participant": "P3", "text": "I cancelled because I could not skip.", "code": "no-pause"},
    {"id": "n08", "participant": "P3", "text": "Pausing should be the default before cancel, not hidden.", "code": "no-pause"},
    {"id": "n09", "participant": "P4", "text": "I did not want to lose what I had already paid.", "code": "money-unclear"},
    {"id": "n10", "participant": "P4", "text": "The settings had nothing about pausing.", "code": "no-pause"},
    {"id": "n11", "participant": "P4", "text": "A wedding trip lasted two weeks. I had to cancel and re-subscribe.", "code": "travel-conflict"},
    {"id": "n12", "participant": "P5", "text": "I assumed the money was gone if I stopped.", "code": "money-unclear"},
    {"id": "n13", "participant": "P5", "text": "The plan page only shows meal photos, not controls.", "code": "no-pause"},
    {"id": "n14", "participant": "P5", "text": "My team went to Goa for an offsite.", "code": "travel-conflict"},
    {"id": "n15", "participant": "P6", "text": "I would have paused if I knew my balance stayed.", "code": "money-unclear"},
    {"id": "n16", "participant": "P6", "text": "Travelling every other week makes a fixed subscription useless.", "code": "travel-conflict"},
    {"id": "n17", "participant": "P6", "text": "I looked for a pause before I looked for a cancel.", "code": "no-pause"},
    {"id": "n18", "participant": "P7", "text": "I did not realise I had already paid for the next week.", "code": "money-unclear"},
    {"id": "n19", "participant": "P7", "text": "A skip button would have saved me.", "code": "no-pause"},
    {"id": "n20", "participant": "P7", "text": "A three-day trip should not mean I lose a whole week.", "code": "travel-conflict"},
    {"id": "n21", "participant": "P8", "text": "I expected the money to roll over, but it did not say.", "code": "money-unclear"},
    {"id": "n22", "participant": "P8", "text": "I could not find any way to pause.", "code": "no-pause"},
    {"id": "n23", "participant": "P8", "text": "I cancelled during a work trip to Delhi.", "code": "travel-conflict"},
    {"id": "n24", "participant": "P8", "text": "Support said I could pause by calling them, but that is hidden.", "code": "no-pause"},
]

# Five insights written from the notes, each citing the note ids that support it
INSIGHTS = [
    {
        "id": "i1",
        "text": "People cancel because they cannot pause, not because they dislike the food.",
        "codes_cited": ["no-pause"],
        "notes_cited": ["n02", "n04", "n07", "n08", "n10", "n13", "n17", "n19", "n22", "n24"],
    },
    {
        "id": "i2",
        "text": "Work and family travel is the most common trigger for wanting a pause.",
        "codes_cited": ["travel-conflict"],
        "notes_cited": ["n01", "n03", "n06", "n11", "n14", "n16", "n20", "n23"],
    },
    {
        "id": "i3",
        "text": "People assume their paid balance is lost if they stop, so they cancel instead of pausing.",
        "codes_cited": ["money-unclear"],
        "notes_cited": ["n05", "n09", "n12", "n15", "n18", "n21"],
    },
    {
        "id": "i4",
        "text": "A pause option that names what happens to the money would prevent most cancellations.",
        "codes_cited": ["no-pause", "money-unclear"],
        "notes_cited": ["n07", "n09", "n15", "n17", "n21"],
    },
    {
        "id": "i5",
        "text": "Support knows how to pause, but the app hides that path from customers.",
        "codes_cited": ["no-pause"],
        "notes_cited": ["n24", "n22", "n19"],
    },
]


def validate_synthesis(notes: list, insights: list) -> dict:
    """Check every note is coded and every insight cites notes from at least two participants."""
    note_by_id = {n["id"]: n for n in notes}
    
    # Check 1: every note must have a code
    orphan_notes = [n["id"] for n in notes if not n.get("code")]
    
    # Check 2: every insight must cite notes from at least two distinct participants
    weak_insights = []
    for ins in insights:
        participants_cited = set()
        for note_id in ins["notes_cited"]:
            if note_id in note_by_id:
                participants_cited.add(note_by_id[note_id]["participant"])
        if len(participants_cited) < 2:
            weak_insights.append({
                "insight_id": ins["id"],
                "participants_count": len(participants_cited),
                "reason": "needs notes from at least two participants",
            })
    
    return {
        "total_notes": len(notes),
        "orphan_notes": orphan_notes,
        "total_insights": len(insights),
        "weak_insights": weak_insights,
    }


def count_participants_per_insight(notes: list, insights: list) -> list:
    """Count how many distinct participants each insight draws from."""
    note_by_id = {n["id"]: n for n in notes}
    result = []
    for ins in insights:
        participants = set()
        for note_id in ins["notes_cited"]:
            if note_id in note_by_id:
                participants.add(note_by_id[note_id]["participant"])
        result.append({
            "insight_id": ins["id"],
            "participants": len(participants),
            "notes": len(ins["notes_cited"]),
        })
    return result


def main() -> None:
    """Run the validation and print the results."""
    print("Synthesis validation for 8 MealBox interviews")
    print("=" * 50)
    
    result = validate_synthesis(NOTES, INSIGHTS)
    
    print(f"\nNotes: {result['total_notes']} total")
    if result["orphan_notes"]:
        print(f"  Orphan notes (no code): {result['orphan_notes']}")
    else:
        print("  All notes coded: pass")
    
    print(f"\nInsights: {result['total_insights']} total")
    if result["weak_insights"]:
        print("  Weak insights (fewer than 2 participants):")
        for w in result["weak_insights"]:
            print(f"    {w['insight_id']}: {w['reason']} (has {w['participants_count']})")
    else:
        print("  All insights have notes from 2+ participants: pass")
    
    print("\nParticipant coverage per insight:")
    coverage = count_participants_per_insight(NOTES, INSIGHTS)
    for c in coverage:
        print(f"  {c['insight_id']}: {c['participants']} participants, {c['notes']} notes")
    
    # Summary
    passes = not result["orphan_notes"] and not result["weak_insights"]
    print(f"\nTraceability check: {'PASS' if passes else 'FAIL'}")


main()

Output

Synthesis validation for 8 MealBox interviews
==================================================

Notes: 24 total
  All notes coded: pass

Insights: 5 total
  All insights have notes from 2+ participants: pass

Participant coverage per insight:
  i1: 8 participants, 10 notes
  i2: 8 participants, 8 notes
  i3: 6 participants, 6 notes
  i4: 4 participants, 5 notes
  i5: 2 participants, 3 notes

Traceability check: PASS

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 affinity_check.py

The validation passes: all 24 notes are coded, and all 5 insights cite notes from at least 2 participants. Insight i1 (“People cancel because they cannot pause”) draws from all 8 participants, the strongest backing in the set. Insight i5 draws from only 2 participants — the minimum, so it would need more evidence before becoming a main finding.

Checkpoint

Check: Keeping insights traceable

  1. Question 1 of 2 The lesson says every insight should cite notes from how many participants at minimum?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: At least two

    Two participants are the minimum because a single person's note might be an outlier. The lesson's validation script enforces this rule.

  2. Question 2 of 2 Why does every note carry a participant id and a timestamp?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: So anyone reading the insight can check which participants said what

    Traceability means a skeptic can follow the chain from an implication to the notes that support it. Without ids, an insight is a claim with no evidence trail.

Counts in qualitative research and AI-assisted synthesis

Counts in qualitative research describe the sample, not the population. “4 of 6 participants paused at the PIN code field” is a fact about those six people; “67 % of users pause at the PIN code field” is a claim about all subscribers, which six interviews cannot support. Write the first form, never the second.

AI-assisted clustering — pasting transcripts into a chatbot and asking it to suggest themes — can speed up a first pass, but it has two risks:

  1. Hallucinated themes: the model may return themes that sound plausible but have no evidence in the notes. Every theme must be checked against the notes it claims to summarise.
  2. Personal data exposure: OWASP’s Top 10 for LLM Applications (2025) names LLM02 as sensitive information disclosure. Redact names, addresses and anything that identifies a participant before any text reaches a third-party endpoint.

Data handling under the DPDP Act

Research transcripts are personal data under India’s Digital Personal Data Protection Act 2023. The 2025 Rules add detail on consent, notice and retention. Pseudonymise transcripts before sharing or processing, and delete recordings on schedule.

Which coding approach fits which goal? Answer the question about your research and the table says which approach still fits:

Interactive · answer and compare Which coding approach for which goal? Answer the question about your research; the table says which approach still fits.
Do you already have a codebook from earlier research on the same question?
Does the timeline leave room for a slow first pass through the notes?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Inductive, deductive and hybrid coding against time, bias and re-usability.
Coding approach Time cost Bias risk Re-usability
Inductive Starts from: the notes themselves Poor read every note before any code exists; slowest first pass Good codes come from what participants said, not what you expected Poor the codes are specific to this round; a new study starts over
Deductive Starts from: a codebook written before the research Good the codebook exists before the notes arrive; fastest to apply Poor you may miss what the codebook did not predict Good the same codebook can track the same themes over time
Hybrid Starts from: a starting codebook that grows Mixed start with a codebook, add codes as the notes demand Mixed less likely to miss patterns, more work than pure deductive Mixed the codebook grows; older codes stay comparable
How the answers decide
  • “Do you already have a codebook from earlier research on the same question?” No, this is new ground and “Does the timeline leave room for a slow first pass through the notes?” No, synthesis must be fast: rules out Inductive. Without an existing codebook and without time, starting from scratch will not finish; hybrid keeps the speed of deductive and the openness to surprise.
  • “Do you already have a codebook from earlier research on the same question?” Yes, we have a codebook: adds a caveat to Inductive. Inductive coding ignores the codebook you already have, so the time it took to build is wasted, but the new codes may reveal what the old book missed.
  • “Does the timeline leave room for a slow first pass through the notes?” Yes, we have time and “Do you already have a codebook from earlier research on the same question?” No, this is new ground: adds a caveat to Deductive. Writing a codebook before a first round forces you to guess the patterns, which raises bias risk and makes the speed benefit small.

Checkpoint

Check: Counts and AI-assisted synthesis

  1. Question 1 of 2 A synthesis says "4 of 6 participants". Why not say "67 % of users"?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Counts describe the sample; "67 % of users" claims something about the population that the sample cannot support

    A qualitative sample describes what happened to those people, not what is true of everyone. Saying "67 % of users" implies a population rate the small sample cannot estimate.

  2. Question 2 of 2 A product manager pastes transcripts into a chatbot. Two of its five themes do not appear in any transcript. What should the researcher do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Check every theme against the notes; keep only those that cite real quotes

    AI can suggest themes quickly, but it can also fabricate ones that sound plausible but have no evidence. Every theme must be traced to at least two participants' notes.

Practise

From the same 24 coded notes, write four insights yourself. Each insight must be a full sentence, cite notes from at least two participants, and avoid topic-style phrasings:

Exercise · Easy · Python

Write insights from coded notes

# Write insights from coded notes

You have 24 coded notes from eight fictional MealBox (mealbox.example) interviews about why people cancel in their first week. The notes already carry codes: travel-conflict, no-pause and money-unclear.

Your task is to write four insights from these notes. Each insight must:

  1. Be a full sentence that states a finding, not a topic (wrong: "Pricing"; right: "People pay more willingly when they can see their delivery day").
  2. Cite the note ids that support it, and those notes must come from at least two different participants.
  3. Not be phrased as a topic ("about pricing", "about delivery") — the test fails topic-style phrasings.

Complete the build_insights() function so it returns a list of four dicts, each with:

  • text: the insight as a sentence
  • cited_notes: a list of note ids (strings like "n01", "n04")

The sample tests check:

  • You return exactly four insights.
  • Every cited note id exists in the data.
  • Every insight cites notes from at least two distinct participants.
  • No insight text matches banned topic phrases.

Good luck — the lesson's validation script uses the same rules.

Starter code · insights.py

"""Write four insights from 24 coded MealBox interview notes.

All data is invented for this exercise. MealBox (mealbox.example) is a fictional
lunch-subscription service; participant ids P1–P8 and quotes are original to this track.
"""

# 24 coded notes from eight fictional participants
NOTES = [
    {"id": "n01", "participant": "P1", "text": "I travel for work a lot, so a fixed weekly plan is pointless.", "code": "travel-conflict"},
    {"id": "n02", "participant": "P1", "text": "I wanted to pause for one week, but there was no option.", "code": "no-pause"},
    {"id": "n03", "participant": "P2", "text": "The first meal arrived while I was at a client site.", "code": "travel-conflict"},
    {"id": "n04", "participant": "P2", "text": "I searched everywhere for a skip button.", "code": "no-pause"},
    {"id": "n05", "participant": "P2", "text": "I have no idea what happens to the money if I pause.", "code": "money-unclear"},
    {"id": "n06", "participant": "P3", "text": "My parents came to visit and I ate at home.", "code": "travel-conflict"},
    {"id": "n07", "participant": "P3", "text": "I cancelled because I could not skip.", "code": "no-pause"},
    {"id": "n08", "participant": "P3", "text": "Pausing should be the default before cancel, not hidden.", "code": "no-pause"},
    {"id": "n09", "participant": "P4", "text": "I did not want to lose what I had already paid.", "code": "money-unclear"},
    {"id": "n10", "participant": "P4", "text": "The settings had nothing about pausing.", "code": "no-pause"},
    {"id": "n11", "participant": "P4", "text": "A wedding trip lasted two weeks. I had to cancel and re-subscribe.", "code": "travel-conflict"},
    {"id": "n12", "participant": "P5", "text": "I assumed the money was gone if I stopped.", "code": "money-unclear"},
    {"id": "n13", "participant": "P5", "text": "The plan page only shows meal photos, not controls.", "code": "no-pause"},
    {"id": "n14", "participant": "P5", "text": "My team went to Goa for an offsite.", "code": "travel-conflict"},
    {"id": "n15", "participant": "P6", "text": "I would have paused if I knew my balance stayed.", "code": "money-unclear"},
    {"id": "n16", "participant": "P6", "text": "Travelling every other week makes a fixed subscription useless.", "code": "travel-conflict"},
    {"id": "n17", "participant": "P6", "text": "I looked for a pause before I looked for a cancel.", "code": "no-pause"},
    {"id": "n18", "participant": "P7", "text": "I did not realise I had already paid for the next week.", "code": "money-unclear"},
    {"id": "n19", "participant": "P7", "text": "A skip button would have saved me.", "code": "no-pause"},
    {"id": "n20", "participant": "P7", "text": "A three-day trip should not mean I lose a whole week.", "code": "travel-conflict"},
    {"id": "n21", "participant": "P8", "text": "I expected the money to roll over, but it did not say.", "code": "money-unclear"},
    {"id": "n22", "participant": "P8", "text": "I could not find any way to pause.", "code": "no-pause"},
    {"id": "n23", "participant": "P8", "text": "I cancelled during a work trip to Delhi.", "code": "travel-conflict"},
    {"id": "n24", "participant": "P8", "text": "Support said I could pause by calling them, but that is hidden.", "code": "no-pause"},
]


def build_insights() -> list:
    """Return a list of four insights, each as a dict with 'text' and 'cited_notes'.

    Each insight must:
    - Have 'text' as a full sentence stating a finding (not a topic).
    - Have 'cited_notes' as a list of note ids (e.g. ["n02", "n07", "n17"]).
    - Cite notes from at least two distinct participants.
    """
    # TODO: Write your four insights here
    return [
        {"text": "...", "cited_notes": []},
        {"text": "...", "cited_notes": []},
        {"text": "...", "cited_notes": []},
        {"text": "...", "cited_notes": []},
    ]


def main() -> None:
    """Print the insights for review."""
    insights = build_insights()
    note_by_id = {n["id"]: n for n in NOTES}
    
    print(f"Insights: {len(insights)}")
    for i, ins in enumerate(insights, 1):
        participants = set()
        for nid in ins.get("cited_notes", []):
            if nid in note_by_id:
                participants.add(note_by_id[nid]["participant"])
        print(f"\n{i}. {ins.get('text', '(no text)')}")
        print(f"   Cited notes: {ins.get('cited_notes', [])}")
        print(f"   Participants: {len(participants)}")


main()
The sample tests · test_insights.py
"""Sample tests for the write-insights-from-notes exercise."""
import re
from insights import build_insights, NOTES


def test_returns_four_insights():
    """Check that exactly four insights are returned."""
    insights = build_insights()
    assert isinstance(insights, list), "build_insights() must return a list"
    assert len(insights) == 4, f"Expected 4 insights, got {len(insights)}"


def test_insights_have_required_keys():
    """Check that each insight has 'text' and 'cited_notes' keys."""
    insights = build_insights()
    for i, ins in enumerate(insights):
        assert "text" in ins, f"Insight {i+1} missing 'text' key"
        assert "cited_notes" in ins, f"Insight {i+1} missing 'cited_notes' key"
        assert isinstance(ins["text"], str), f"Insight {i+1} 'text' must be a string"
        assert isinstance(ins["cited_notes"], list), f"Insight {i+1} 'cited_notes' must be a list"


def test_all_cited_notes_exist():
    """Check that every cited note id exists in the data."""
    insights = build_insights()
    note_ids = {n["id"] for n in NOTES}
    for i, ins in enumerate(insights):
        for nid in ins.get("cited_notes", []):
            assert nid in note_ids, f"Insight {i+1} cites unknown note '{nid}'"


def test_each_insight_cites_at_least_two_participants():
    """Check that each insight cites notes from at least two distinct participants."""
    insights = build_insights()
    note_by_id = {n["id"]: n for n in NOTES}
    for i, ins in enumerate(insights):
        participants = set()
        for nid in ins.get("cited_notes", []):
            if nid in note_by_id:
                participants.add(note_by_id[nid]["participant"])
        assert len(participants) >= 2, (
            f"Insight {i+1} cites notes from only {len(participants)} participant(s); "
            f"need at least 2"
        )


def test_no_topic_phrasing():
    """Check that insight text is not phrased as a topic."""
    banned_patterns = [
        r"\babout\s+(pricing|delivery|pausing|money|travel)",
        r"^(pricing|delivery|pausing|money|travel)$",
        r"^[A-Z][a-z]+$",  # Single capitalised word like "Pricing"
    ]
    insights = build_insights()
    for i, ins in enumerate(insights):
        text = ins.get("text", "").strip().lower()
        for pattern in banned_patterns:
            match = re.search(pattern, text, re.IGNORECASE)
            assert not match, (
                f"Insight {i+1} looks like a topic, not a sentence: '{ins.get('text')}'. "
                f"Write it as a full sentence stating a finding."
            )
A hint

# Hint: start from the codes

Look at the three codes in the data: travel-conflict, no-pause and money-unclear.

Each code groups several notes. Your insight should interpret what those notes mean together. For example, notes coded no-pause from P1, P2, P3, P4, P5, P6, P7 and P8 all say they could not skip a week. What does that pattern tell you about why people cancelled?

Write a sentence that states the finding:

{
    "text": "People cancel because they cannot pause, not because they dislike the food.",
    "cited_notes": ["n02", "n04", "n07", "n10", "n13", "n17", "n19", "n22"],
}

That insight cites eight notes from eight different participants — well above the minimum of two.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

After the exercise, a few questions cover every section and return to the ones you answer least surely:

Practice that adapts to you

Practise this lesson

8 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 A theme called "money concerns" covers six notes. Two mention not knowing what happens to the balance; four mention money as the reason they cancelled instead of pausing. What should the researcher do?

    Choose one answer.

    Show the answer: Question 1 of 8

    Answer: Split the theme into "balance feels lost" and "money blocks pause"

    Reviewing themes against the notes is phase 4 of thematic analysis. When a theme holds two distinct patterns, splitting it keeps each one actionable.

  2. Question 2 of 8 An insight says "People assume their paid balance is lost if they stop." Which implication follows?

    Choose one answer.

    Show the answer: Question 2 of 8

    Answer: Show what happens to the balance before the pause control

    An implication is what the product lead can do. Here, the assumption is the problem, so the action is to show the rule that clears it up.

  3. Question 3 of 8 A stakeholder says the insight "People cancel because they cannot pause" sounds made up. How does traceability help?

    Choose one answer.

    Show the answer: Question 3 of 8

    Answer: Show the stakeholder the ten notes from eight participants that support it

    Traceability is the answer to "show me". The insight cites ten notes from eight participants; the stakeholder can read them and disagree with the interpretation, but not with the evidence.

  4. Question 4 of 8 Before pasting interview transcripts into an AI tool, what should the researcher do?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: Redact personal data, including names and anything that identifies a participant

    OWASP's Top 10 for LLM Applications names LLM02 as sensitive data exposure. The fix is to redact before the text reaches any third-party endpoint.

  5. Question 5 of 8 A board after grouping shows three sticky notes under the word "Pricing". Is "Pricing" a theme?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: No, it is a topic; a theme is a sentence that says what the notes mean

    "Pricing" is a category, not a claim. "People pay more willingly when they can see their delivery day" is a theme because it says what the notes mean.

  6. Question 6 of 8 A team has a codebook from a previous round on the same question. What is the risk of using it again?

    Choose one answer.

    Show the answer: Question 6 of 8

    Answer: The codebook may miss patterns it did not predict

    Deductive coding is faster but may miss what the codebook did not anticipate. Hybrid coding keeps the codebook but adds codes as the notes demand.

  7. Question 7 of 8 A product manager says "I heard a customer complain about pausing." How is an insight different?

    Choose one answer.

    Show the answer: Question 7 of 8

    Answer: An insight cites notes from at least two participants and names the evidence

    An anecdote is a memory; an insight is a claim with a trail anyone can check. The product manager's story may be true, but it is not evidence until it links to notes.

  8. Question 8 of 8 In qualitative research, what does a count such as "4 of 6 participants" describe?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: The six people who took part in the study

    Counts in qualitative research describe the sample, not the population. Write "4 of 6 participants", and never turn it into "67 % of users".

Interview questions

Warm-up (fresher to 2–5 years): how are themes different from topics?

A topic is a single word or phrase that names a category: “Pricing”, “Delivery”, “Trust”. It tells you what the notes are about, but not what they say. A theme is a sentence that interprets a pattern: “People pay more willingly when they can see their delivery day.” A theme is actionable because it states a claim; a topic leaves the claim unspoken. In the MealBox synthesis, the topic might be “pausing” but the theme is “People cancel because they cannot pause, not because they dislike the food.” The difference is the difference between a label and a finding.

Two harder questions follow: one about turning notes into insights, one about AI-assisted clustering. Plan how you would open each answer before you look at the first step its page suggests:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to senior

    A MealBox manager hands you 60 notes and asks for five bullet insights by tomorrow. Walk me through how you would do it.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to staff

    A product manager pastes transcripts into a chatbot that returns five themes. Two of the themes do not appear in any transcript. What do you do and how do you prevent it?

    Model answer, Pro. First step and follow-ups

Flashcards · 7 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What are the six phases of thematic analysis, and how does an affinity diagram relate to them?

    Familiarise, code, search for themes, review them against the notes, define them, and write up. An affinity diagram is the visual, bottom-up version of the same loop.

  2. What happens to a theme that fails to hold the notes under it?

    It splits or merges. Thematic analysis is a loop, not a single pass: codes emerge from notes, themes from codes, and each theme is checked against the notes again.

  3. How do an observation, an insight and an implication differ?

    An observation states what happened to the sample ("4 of 6 participants paused at the PIN code field"), an insight interprets the pattern ("people trust delivery only after they see their area is served"), and an implication says what to do ("check the PIN code before plan choice").

  4. A synthesis board has labels like "Pricing", "Delivery" and "Trust". What is wrong?

    They are topics, not themes. A theme label is a sentence that says what was found; a single word is only a category.

  5. How many participants must an insight cite notes from, and why?

    At least two, so a single person's note cannot carry a claim on its own, and so a sceptic can trace the insight back to the evidence. Every note keeps its participant id and, when recorded, a timestamp.

  6. Your synthesis found that 4 of 6 participants missed a field. Can you report 67 % of users?

    No. Counts in qualitative research describe the sample ("4 of 6"), not the population; a rate needs a method that counts a representative sample.

  7. What two risks come with clustering research notes with an AI model?

    Hallucinated themes, which sound plausible yet rest on nothing in the notes, so every theme is checked against them; and personal data exposure (OWASP LLM02, sensitive information disclosure), so names, addresses and identifying details are redacted before any text reaches a third-party model.

Key takeaways

  • Thematic analysis is a loop of six phases: familiarise, code, search for themes, review them against the notes, define them, and write up. An affinity diagram is the visual, bottom-up version of the same loop.
  • An observation states what happened to the sample; an insight interprets the pattern; an implication says what to do. Write insights as sentences, not as single-word topics.
  • Every insight must cite notes from at least two participants, so a skeptic can trace the claim back to evidence and check it.
  • Counts in qualitative research describe the sample (“4 of 6”), not the population (“67 % of users”).
  • AI-assisted clustering may hallucinate themes that have no evidence; check every theme against the notes, and redact personal data before sending text to any third-party model.

Terms in this lesson

affinity diagram
A visual method of grouping notes into clusters on a wall or board, where themes emerge from the notes themselves rather than from a pre-written codebook.
evidence-insight-implication
A three-part way of writing up a research finding: the evidence (what was observed), the insight (what it means) and the implication (what to do about it).
insight (UX)
A sentence that interprets a pattern in research data and states what it means, actionable because it says something a product lead can respond to.
thematic analysis
A method for working through qualitative notes in six phases: familiarise, code, search for themes, review against the notes, define themes and write up.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.