Module 3: Target And Positioning

The job, not the demographics

Description

So far you defined segments by their importance weights — explorers values curatedDiscovery, exactSkuShoppers values price and deliverySpeed — without asking where those weights come from. This lesson answers that question with Clayton Christensen's framework: a segment isn't defined by who the person is (age, income, city), but by the job they're trying to get done at the moment of buying. The phrase Christensen made famous is literal: people don't buy products, they hire products to do a job for them. If the product does the job well, they "rehire" it next time. If not, they "fire" it and hire something else.

This isn't a cosmetic vocabulary change — it completely changes which questions make sense to ask. "Who is our customer?" invites you to describe age, gender, income, city. "What job are they trying to get done?" invites you to describe a situation and an intent — and, as you'll see run in this lesson, the exact same person, exactly the same person, can have completely different jobs depending on the moment, with opposite importance weights from each other.

Connection to the module. Lessons 2 and 3 gave you positionFit's mechanics with already-defined segments. This lesson explains where those weights should come from: not from a demographic profile, but from the specific job the person brings at that moment of purchase. You're going to run positionFit with two jobs — not two types of person — and you're going to see that the same buyer, with the wrong job in the product team's head, produces the same kind of structural defeat you already saw with exactSkuShoppers.

An everyday analogy: Christensen's milkshake

Christensen tells a real case that became the most cited example in the whole JTBD framework: a fast-food chain wanted to sell more milkshakes and hired researchers to profile "the typical milkshake buyer" — age, income, whether they had kids. With that profile, they tested product variations (thicker, less sweet, with fruit chunks) and none consistently moved sales.

So they changed the question: instead of asking who bought milkshakes, they sat down and observed when they bought them and what job they were solving at that moment. They found two completely different jobs, bought by the same type of person at different times of day. In the morning, someone driving alone to work "hired" a milkshake for a very specific job: something filling, that could be drunk with one hand on a long, boring commute, without making a mess in the car — the milkshake competed against a bagel, a banana, or nothing at all. In the afternoon, a parent with their kids "hired" the same milkshake for a different job: a quick, guilt-free treat that indulged the kid without eating up the whole afternoon — there, the milkshake competed against ice cream or a happy-meal toy. The same person, the same store, the same product — but two jobs, with opposite priorities, at two moments of the same day. No variation of "thicker" or "with fruit" was going to move both occasions at once, because they weren't the same buyer solving the same problem — they were two different problems that happened to share the same packaging.

Worked example: two jobs, the same type of buyer, opposite verdicts

We define two jobs for Mercado, not two demographic profiles: giftDiscoveryJob ("I need to find something special for someone, without knowing exactly what") and restockJob ("I need to restock something specific I already know I use, as fast and cheap as possible"). The same person — imagine someone getting ready for a birthday this week — could bring either job to Mercado at different moments of the same month.

function positionFit(target, product, alternatives) {
  const dims = Object.keys(target.weights).filter((d) => target.weights[d] > 0);
  const weightedScore = (c) => dims.reduce((sum, d) => sum + target.weights[d] * c.scores[d], 0);
  const productScore = weightedScore(product);
  const rivals = alternatives.map((a) => ({ name: a.name, score: weightedScore(a) }));
  const bestRival = rivals.reduce((a, b) => (b.score > a.score ? b : a));
  const byDimension = dims.map((d) => {
    const rivalBest = alternatives.reduce(
      (best, a) => (a.scores[d] > best.score ? { name: a.name, score: a.scores[d] } : best),
      { name: alternatives[0].name, score: -Infinity }
    );
    return { dimension: d, weight: target.weights[d], productScore: product.scores[d], bestRivalScore: rivalBest.score, bestRivalName: rivalBest.name, wins: product.scores[d] > rivalBest.score };
  });
  return { segment: target.name, productWeightedScore: Number(productScore.toFixed(2)), bestRival: bestRival.name, bestRivalWeightedScore: Number(bestRival.score.toFixed(2)), fitsSegment: productScore > bestRival.score, byDimension };
}

const mercado = { name: 'Mercado', scores: { curatedDiscovery: 9, sellerTrust: 8, catalogBreadth: 6, price: 5, deliverySpeed: 5, convenience: 6 } };
const genericMegastore = { name: 'genericMegastore', scores: { curatedDiscovery: 3, sellerTrust: 4, catalogBreadth: 9, price: 8, deliverySpeed: 9, convenience: 8 } };
const localShop = { name: 'localShop', scores: { curatedDiscovery: 6, sellerTrust: 9, catalogBreadth: 2, price: 4, deliverySpeed: 3, convenience: 3 } };
const alternatives = [genericMegastore, localShop];

// The job, not the person: "I need to find something special, without knowing exactly what".
const giftDiscoveryJob = { name: 'giftDiscoveryJob', weights: { curatedDiscovery: 0.45, sellerTrust: 0.35, catalogBreadth: 0.05, price: 0.05, deliverySpeed: 0.05, convenience: 0.05 } };
// The same type of buyer, a different job: "I need to restock THIS, now, cheaply".
const restockJob = { name: 'restockJob', weights: { curatedDiscovery: 0, sellerTrust: 0.05, catalogBreadth: 0.1, price: 0.3, deliverySpeed: 0.4, convenience: 0.15 } };

console.log('=== positionFit: giftDiscoveryJob ===\n');
const r1 = positionFit(giftDiscoveryJob, mercado, alternatives);
console.log(`segment: ${r1.segment} | productWeightedScore: ${r1.productWeightedScore} | bestRival: ${r1.bestRival} (${r1.bestRivalWeightedScore}) | fitsSegment: ${r1.fitsSegment}\n`);
console.table(r1.byDimension.map((d) => ({ dimension: d.dimension, weight: d.weight, product: d.productScore, bestRival: `${d.bestRivalName}:${d.bestRivalScore}`, wins: d.wins })));

console.log('\n=== positionFit: restockJob ===\n');
const r2 = positionFit(restockJob, mercado, alternatives);
console.log(`segment: ${r2.segment} | productWeightedScore: ${r2.productWeightedScore} | bestRival: ${r2.bestRival} (${r2.bestRivalWeightedScore}) | fitsSegment: ${r2.fitsSegment}\n`);
console.table(r2.byDimension.map((d) => ({ dimension: d.dimension, weight: d.weight, product: d.productScore, bestRival: `${d.bestRivalName}:${d.bestRivalScore}`, wins: d.wins })));

What to expect. Running the file with Node, the output is exactly this:

=== positionFit: giftDiscoveryJob ===

segment: giftDiscoveryJob | productWeightedScore: 7.95 | bestRival: localShop (6.45) | fitsSegment: true

┌─────────┬────────────────────┬────────┬─────────┬──────────────────────┬───────┐
│ (index) │     dimension      │ weight │ product │      bestRival       │ wins  │
├─────────┼────────────────────┼────────┼─────────┼──────────────────────┼───────┤
│    0    │ 'curatedDiscovery' │  0.45  │    9    │    'localShop:6'     │ true  │
│    1    │   'sellerTrust'    │  0.35  │    8    │    'localShop:9'     │ false │
│    2    │  'catalogBreadth'  │  0.05  │    6    │ 'genericMegastore:9' │ false │
│    3    │      'price'       │  0.05  │    5    │ 'genericMegastore:8' │ false │
│    4    │  'deliverySpeed'   │  0.05  │    5    │ 'genericMegastore:9' │ false │
│    5    │   'convenience'    │  0.05  │    6    │ 'genericMegastore:8' │ false │
└─────────┴────────────────────┴────────┴─────────┴──────────────────────┴───────┘

=== positionFit: restockJob ===

segment: restockJob | productWeightedScore: 5.4 | bestRival: genericMegastore (8.3) | fitsSegment: false

┌─────────┬──────────────────┬────────┬─────────┬──────────────────────┬───────┐
│ (index) │    dimension     │ weight │ product │      bestRival       │ wins  │
├─────────┼──────────────────┼────────┼─────────┼──────────────────────┼───────┤
│    0    │  'sellerTrust'   │  0.05  │    8    │    'localShop:9'     │ false │
│    1    │ 'catalogBreadth' │  0.1   │    6    │ 'genericMegastore:9' │ false │
│    2    │     'price'      │  0.3   │    5    │ 'genericMegastore:8' │ false │
│    3    │ 'deliverySpeed'  │  0.4   │    5    │ 'genericMegastore:9' │ false │
│    4    │  'convenience'   │  0.15  │    6    │ 'genericMegastore:8' │ false │
└─────────┴──────────────────┴────────┴─────────┴──────────────────────┴───────┘

Notice something lesson 2 couldn't show you yet: giftDiscoveryJob wins with an even bigger margin than explorers (7.95 against 6.45, a margin of 1.5) — it is, in effect, almost the same segment as explorers, just now named by the job instead of by a generic label. That isn't a coincidence: explorers was always, without saying it in those words, people with the job "help me discover something I didn't know I wanted" — naming it as a job instead of as a type of person doesn't change the result, it makes it more precise and easier to defend to anyone who asks "why those weights and not others?"

And restockJob reproduces, almost number for number, exactSkuShoppers's defeat: 5.4 against the generic megastore's 8.3, losing all five dimensions that matter to it. The crucial difference from lesson 2 is this: restockJob isn't necessarily "a different type of person" — it could be the same person who brought giftDiscoveryJob last week, now with a different job in mind (they ran out of detergent, they need to restock it right now). If Mercado's team thinks "we already won this user, they'll always prefer us," they're making the same mistake the milkshake chain made asking "who buys milkshakes?" instead of "what job does this person bring right now?"

Going deeper: a job has three dimensions, not one

Christensen and his colleagues describe a complete job as more than a functional task — it has three layers, and all three matter for precisely defining a segment's weights:

  1. Functional: what concrete task needs solving. For giftDiscoveryJob: "find a specific item someone else will like."
  2. Emotional: how the person wants to feel while solving it, or after solving it. For giftDiscoveryJob: they want to feel clever, not rushed — part of why deliverySpeed weighs so little (0.05) in this job: speed isn't the point, surprise is.
  3. Social: how they want to be perceived by others while solving it. For giftDiscoveryJob: they want the gift to say "I thought of you specifically," not "I bought the first thing that showed up" — another reason sellerTrust (the human curation behind the seller) weighs so heavily (0.35).

restockJob has almost no emotional or social component — it's, almost purely, functional: restock something as fast and cheaply as possible, with no narrative around it. That difference in composition — not just urgency — is what separates one job from another, and it's why their weights end up so different from each other.

Common mistakes

Confusing the demographic segment with the job. What happens: the team describes the target segment as "urban women aged 28-40, middle-to-upper income" and uses that description, as is, as if it were enough to decide which dimensions to prioritize in the product. Why it happens: demographic data is easy to get (surveys, analytics, payment data) and feels "objective" and measurable, while naming a job demands a harder-to-defend interpretation with a single figure. How to spot it: ask whether the segment description explains what the person is trying to achieve, or only who they are. "Women 28-40" doesn't distinguish between someone with giftDiscoveryJob and that same person, a different day, with restockJob — and yet, as you saw run, those two jobs need an almost opposite product to win. How to fix it: for every demographic segment you define, ask "what specific job does this person bring to Mercado at the moment of purchase?" — and define positionFit's weights by that job, not by the demographic profile.

Assuming the same buyer always brings the same job. What happens: the product team, once it "wins" a type of user, assumes that user will prefer Mercado on any future buying occasion, without distinguishing between the different jobs that same person might bring at different moments. Why it happens: it's simpler to think of "won users" as a fixed category than of "job occasions" that change with context — Christensen's milkshake is exactly this mistake, corrected. How to spot it: if your retention analysis groups all of a user's purchases as if they were the same type of decision, without distinguishing each occasion's job, you're probably repeating the "typical milkshake buyer" mistake. How to fix it: segment purchase occasions by job, not just users by profile — the same person with giftDiscoveryJob this week and restockJob next week needs, on each occasion, Mercado to serve that moment's job well, not a fixed profile calculated once.

Defining the job so broadly that any product would solve it equally well. What happens: the job gets written as "I want to buy good things" or "I want a good shopping experience" — sounding specific, but not translating into weights that distinguish one product from another. Why it happens: a vague job is easier to write in one inspiring sentence, just like an empty vision (module 2, lesson 6) is easier to write than one with real edge. How to spot it: if translating the job into positionFit weights leaves you with an even split across the six dimensions (the same allShoppersAverage problem from lesson 3), your "job" actually distinguished nothing — it was a generic aspiration under another name. How to fix it: a well-defined job always leaves some dimensions at zero or near-zero weight — like curatedDiscovery in restockJob, which that buyer literally doesn't care about at that moment. If your job rules out no dimension, it still isn't specific.

Exercises

Exercise 1 — Name the job before calculating the weights. For each situation, write the job in one sentence (following the pattern "I need [task], to feel/be perceived as [emotional/social]"), and predict whether its weights would look more like giftDiscoveryJob or restockJob:

  • (a) Someone ran out of toilet paper this morning.
  • (b) Someone wants to surprise their partner on their anniversary with something they'd never have imagined.
See solution
  • (a) Job: "I need to restock toilet paper today, at the most reasonable price, without overthinking it." Looks like restockJob — functional, urgent, with no significant emotional or social component. deliverySpeed and price would weigh more than curatedDiscovery.
  • (b) Job: "I need to find something my partner would never expect, to feel creative and have them perceive how well I know them." Looks like giftDiscoveryJob — the emotional component (feeling clever) and social component (being perceived as someone attentive) dominate over speed or price. curatedDiscovery and sellerTrust would weigh more.

Exercise 2 — The same buyer, two jobs, same week. Explain, in 2-3 sentences, why it would be a mistake for Mercado's team to treat the person from exercise 1(b) — someone who just had a great gift-discovery experience — as "a user who already prefers Mercado," and automatically recommend the platform the next time that same person needs to restock toilet paper.

See solution

It would be the same mistake as Christensen's milkshake: treating the person, not the job, as the unit of analysis. If that same person comes back with restockJob (restocking something urgent and cheap), positionFit already showed that Mercado clearly loses that job against the generic megastore (5.4 against 8.3) — the fact that Mercado won a different job for them the week before doesn't change this new job's result. Recommending Mercado without distinguishing the buyer's current job risks a bad experience (buying something urgent on a platform optimized for browsing) that could damage the trust earned on the previous occasion, instead of reinforcing it.

Exercise 3 — Translate a job into weights, defending each number. Define the weights for a new job: "I need to quickly equip my kid's first bicycle before their birthday this weekend, without overspending, but trusting that the seller knows about kids' bikes." For each of the six dimensions, justify in one sentence why you'd give it a high, medium, or low weight.

See solution

A reasonable split: { curatedDiscovery: 0.05, sellerTrust: 0.25, catalogBreadth: 0.1, price: 0.2, deliverySpeed: 0.35, convenience: 0.05 }. Justification: deliverySpeed high (0.35) because there's a hard deadline, the birthday. sellerTrust high (0.25) because the job requires trusting the seller knows about kids' bikes specifically, not just any seller. price medium (0.2) because "without overspending" matters, but it isn't dominant. curatedDiscovery low (0.05) because they're not exploring what to give — they already know exactly what they need (a bicycle), they just need to find it in time. catalogBreadth and convenience low, secondary compared to urgency and trust. The exercise matters because it shows an "urgent" job (deliverySpeed high) isn't automatically identical to restockJob — this one still carries a trust component (sellerTrust) that restockJob didn't have, because the job itself is different, even though it shares the urgency.

Summary and next step

A well-defined segment doesn't describe who the person is — it describes what job they're trying to get done, with its three layers (functional, emotional, social). You saw, run, that naming the segment by the job (giftDiscoveryJob) doesn't change positionFit's mechanics, but it does make it more precise and more defensible than a generic label — and that the same type of buyer, with a different job (restockJob), can lose with the same structural clarity you already saw in lesson 2. Christensen's milkshake lesson gets confirmed with numbers: the right unit of segmentation is the occasion and the job, not the person's demographic profile.

Before moving on you should be able to: describe any segment as a three-layer job, not as a demographic; and explain why the same person might need completely different products depending on the job they bring at a specific moment.

With the segment and the job now precisely defined, lesson 5 takes the next step: what does it actually mean to "position" for that job? It isn't just winning the positionFit score — it's occupying, in that buyer's mind, a specific, recognizable category, instead of competing on everything at once.

Resources

  • Clayton M. Christensen and Taddy Hall, "Know Your Customers' Jobs to Be Done" — hbr.org/2016/09/know-your-customers-jobs-to-be-done. The source of this lesson's complete framework, including the milkshake case in its original form. In English.
  • Harvard Business School Online, "Clay Christensen's Milkshake Marketing" — library.hbs.edu/working-knowledge/clay-christensens-milkshake-marketing. A longer explanation of the milkshake case, with detail on the two different purchase occasions. In English.
  • April Dunford, Obviously Awesomeaprildunford.com/books. Dunford builds her own positioning process on the same idea: start with the problem the customer solves, not the customer's profile. In English.
  • Geoffrey Moore, Crossing the Chasmgeoffreyamoore.com/book/crossing-the-chasm. Lesson 3's beachhead gets chosen, in practice, by a shared job within a segment — not by a list of demographic attributes. In English.