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.
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:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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".
- 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.
- 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
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
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 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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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
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:
- 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.
- 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:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| 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
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:
- 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").
- Cite the note ids that support it, and those notes must come from at least two different participants.
- 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 sentencecited_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.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
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.
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.
-
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.
-
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?
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
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.
-
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.
-
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").
-
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.
-
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.
-
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.
-
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
- Using thematic analysis in psychology (Qualitative Research in Psychology (Braun and Clarke))
- Analyse a research session (Government Digital Service (GOV.UK))
- Sharing user research findings (Government Digital Service (GOV.UK))
- OWASP Top 10 for LLM Applications 2025 (OWASP Foundation)
- The Digital Personal Data Protection Act, 2023 (Government of India, Ministry of Electronics and Information Technology)
- The Digital Personal Data Protection Rules, 2025 (Government of India, Ministry of Electronics and Information Technology)
- RFC 2606: Reserved Top Level DNS Names (Internet Engineering Task Force (IETF))
Report a problem with this lesson
Kept only in this browser. Your Learn progress