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.

Resume, Portfolio & GitHub Profile  Module 3 – Writing content that proves your work

Resume bullet points that show impact

How to write resume bullets that show impact: what changed, by how much and how, results instead of duties, precise verbs, and how many bullets each job gets.

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

What you will learn

  • Write bullets that follow the accomplished X, measured by Y, by doing Z pattern
  • Rewrite duty statements as results, with the method you used
  • Choose precise action verbs and keep tense, voice and pronouns consistent
  • Limit each bullet to two lines and each role to three to five bullets
  • Order each role's bullets by their relevance to the posting

Before you start

What you’ll learn · Before you start

What you will learn

  • Write bullets that follow the accomplished X, measured by Y, by doing Z pattern
  • Rewrite duty statements as results, with the method you used
  • Choose precise action verbs and keep tense, voice and pronouns consistent
  • Limit each bullet to two lines and each role to three to five bullets
  • Order each role's bullets by their relevance to the posting

Before you start

On this page

“Worked on caching” tells a reader that a cache existed. “Cut p95 API latency from 420 ms to 180 ms by adding PostgreSQL indexes and a Redis cache for route lookups” tells them what changed, by how much and how, in 21 words. Google’s hiring page asks for that shape in a formula: “accomplished [X] as measured by [Y], by doing [Z]”.

This lesson builds bullets of that kind. You will take one apart, turn duties into results, choose verbs that say what you did, and decide how many bullets each job gets and which goes first. A checker that runs in your browser tests twenty bullets against five rules, and a lab has you write a check for invented numbers and rewrite an intern’s eight bullets from his records.

A strong bullet says what changed, by how much, and how

Asha, the invented backend engineer this track follows, wrote “Worked on caching” under her current job. It is true, and it says nothing a reader can use: no change, no size, no sign of which part was hers. Three official guides ask for the same repair in different words. Google’s formula wants what you accomplished, how it was measured and what you did. MIT’s careers office describes PAR statements: a strong action verb, the project, then the result, answering “What did you do?”, “Why did you do it?” and “What was the outcome?”. The UK’s National Careers Service suggests 2 to 3 lines per job using the STAR method: A way to describe one example of your work in four parts: the situation, the task, the action you took and the result. The UK's National Careers Service suggests it for CVs, application forms and interviews.: situation, task, action and result.

Each guide ends in a result you can point to. Step through Asha’s draft becoming a bullet, one part at a time:

Interactive · stage by stage One bullet, built from a draft one part at a time Step through Asha's draft line becoming a bullet. Each step adds one part and says what that part tells a reader.
One bullet, built from a draft one part at a time, step 7 of 7. Showing The draft (replaced by the bullet), 1. An action verb, 2. What changed (X), 3. By how much (Y), 4. How you did it (Z), 5. Where, if it helps.The draftreplaced by the bullet1. An action verbCut2. What changed (X)p95 API latency3. By how much (Y)from 420 ms to 180 ms4. How you did it (Z)indexes and a Redis cache5. Where, if it helpsfor route lookups

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

Every step as text
  1. The draft. Asha's first line for her current job reads 'Worked on caching'. It tells a reader that a cache existed. It names no change, no number and no part of the work that was hers.
  2. A verb first. Ask what she did to the system. She made something faster, so the bullet starts with Cut: an action verb that already promises a before and an after.
  3. What changed. Name the thing that changed, the X of the formula: the p95 latency of the API, the time within which 95 of every 100 requests were answered.
  4. By how much. Give the measure, the Y: from 420 ms to 180 ms. A number with its baseline and its unit lets a reader judge the size of the change, and an interviewer ask how it was measured.
  5. How. Say how she did it, the Z: by adding PostgreSQL indexes and a Redis cache. The method is what an interviewer asks about first, and it puts the skills inside the evidence.
  6. Where, if it helps. Add the scope only when it adds something. For route lookups tells the reader which part of the system got faster; the size of a team or a volume of work can go here too.
  7. The bullet. Read top to bottom, the parts make one line of 21 words: Cut p95 API latency from 420 ms to 180 ms by adding PostgreSQL indexes and a Redis cache for route lookups.

The parts land in a steady order: the verb first, then what changed and by how much, then the method. The order can bend. When a posting asks for a technology, “by adding a Redis cache” may come before the numbers. What cannot go is the change with its measure, because a bullet without them is a duty again. Google also asks about scope, such as how big a team was or what the work covered; add it when it tells the reader something, as “for route lookups” does here.

Key idea

A bullet earns its line when it says what changed, by how much and how you did it. The verb, the measure and the method are the parts a reader scans for and an interviewer asks about.

Checkpoint

Check: What a strong bullet holds

  1. Question 1 of 2 Which part of the formula is missing from "Sped up the nightly build by caching Docker layers"?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The measure, Y

    Google's formula asks for what you accomplished (X), how it was measured (Y) and what you did (Z). This line has X and Z and leaves out Y.

  2. Question 2 of 2 Asha moved 14 services to Kubernetes with Terraform. Which line follows the X, Y, Z pattern?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Moved 14 services to Docker and Kubernetes through infrastructure-as-code in Terraform.

    A bullet in the pattern starts with an action verb, says what changed with a measure, and names how. Here the measure is the scope, 14 services.

Duties describe the job; results describe what you did in it

Canada’s Job Bank puts the problem plainly: a job title already tells an employer what your duties were, so a resume should highlight achievements instead. It also warns against vague statements, quoting “Responsible for improving efficiencies and making cost savings” as a line that gives an employer no information. MIT’s careers office says the same of experience descriptions: they should not read like job descriptions.

A duty becomes a result when you answer three questions about it, which is MySmartCoPilot’s method: what was different after your work, how do you know, and what did you do to make it so? The answers are the X, the Y and the Z. Four rewrites from invented resumes show the questions at work:

Before: a duty After: a result What the rewrite added
Responsible for the hostel mess feedback form. Designed a two-question mess feedback form that collected 1,860 responses in one term; the committee changed three dishes. What changed, and its size
We made the fest website. Built the registration and payment pages of the college fest website, used by 1,140 students in its first week. Your own part
Was tasked with writing unit tests. Wrote 140 unit tests for the billing module, raising its line coverage from 48 % to 81 %. A baseline for the number
Contributed to open source. Fixed a date-parsing bug in an open-source CSV library; the maintainers merged the pull request. An outcome someone can check

The program below holds ten weak bullets of this kind and their rewrites. It checks each one against five rules of form: an action verb first, no personal pronoun, no duty phrase, a measure or a named outcome, and about 200 characters at most, which is two printed lines. Press Edit, put your own bullets in MINE, and run it again:

Twenty bullets, five rules of form Python · bullet_check.py
# Checks resume bullets against five rules of form (MySmartCoPilot's checker; the rules follow the guides this lesson
# cites): it starts with an action verb, it has no personal pronoun, it has no duty phrase, it has a measure or a named
# outcome, and it fits in two printed lines (about 200 characters). It runs on ten weak bullets from invented resumes
# and their rewrites, then on one rewrite with a false number. Press Edit, add your own bullets to MINE, and run it.
import re

VERBS = {
    "added", "automated", "built", "cut", "designed", "fixed", "led", "migrated", "moved", "ran",
    "raised", "reduced", "replaced", "tested", "trained", "wrote",
}
PRONOUNS = {"i", "me", "my", "we", "our", "us"}
DUTY_PHRASES = ("responsible for", "worked on", "helped with", "involved in", "tasked with", "duties included")
OUTCOME_WORDS = ("merged", "adopted", "published", "won", "replaced")
MAX_CHARACTERS = 200

PAIRS = [
    ("Canteen queue app", "a verb and what changed",
     "Worked on the canteen queue app.",
     "Built a token queue for the college canteen in Flask and SQLite, cutting the average lunch-hour wait "
     "from 14 to 6 minutes."),
    ("Hostel mess feedback", "a result, not a duty",
     "Responsible for the hostel mess feedback form.",
     "Designed a two-question mess feedback form that collected 1,860 responses in one term; the committee "
     "changed three dishes."),
    ("College fest website", "your part, not the team's",
     "We made the fest website.",
     "Built the registration and payment pages of the college fest website, used by 1,140 students in its "
     "first week."),
    ("Route lookups at Example Logistics", "a measure and the method",
     "Helped with performance improvements on the API.",
     "Cut p95 API latency from 420 ms to 180 ms by adding PostgreSQL indexes and a Redis cache for route lookups."),
    ("Shipment events at Example Logistics", "the scope",
     "Involved in building a Kafka consumer.",
     "Built a Kafka consumer for 2 million shipment events a day, with idempotent writes and Grafana alerts."),
    ("Billing tests in an internship", "a baseline for the number",
     "Was tasked with writing unit tests.",
     "Wrote 140 unit tests for the billing module, raising its line coverage from 48 % to 81 %."),
    ("Invoice export", "no pronouns, and an honest estimate",
     "I automated some reports for the finance team.",
     "Automated the weekly invoice export with a Python script, saving the finance team about 3 hours a week."),
    ("Library seat booking", "a result in place of upkeep",
     "Duties included maintaining the library seat booking site.",
     "Added e-mail reminders to the library seat booking site, reducing no-shows from 31 % to 12 % in one "
     "semester."),
    ("Python workshops", "what changed for other people",
     "Helped juniors learn Python.",
     "Ran six weekend Python workshops for 45 first-year students; 38 of them finished every exercise."),
    ("Open-source fix", "a named outcome, when there is no number",
     "Contributed to an open-source CSV library over the summer, where I read the code base, joined the "
     "discussions on several issues, worked on a bug about dates and learned a lot about testing and code review.",
     "Fixed a date-parsing bug in an open-source CSV library; the maintainers merged the pull request."),
]
FALSE_NUMBER = "Cut p95 API latency from 420 ms to 18 ms by adding PostgreSQL indexes and a Redis cache for route lookups."
MINE = []  # your own bullets, one string each


def problems(bullet):
    """The rules a bullet breaks, in words (an empty list when it passes all five)."""
    found = []
    words = re.findall(r"[A-Za-z][A-Za-z'-]*", bullet)
    first = words[0].lower() if words else ""
    if first not in VERBS:
        found.append(f'starts with "{words[0] if words else bullet}", not an action verb')
    pronouns = sorted({w for w in words if w.lower() in PRONOUNS})
    if pronouns:
        found.append("a pronoun: " + ", ".join(pronouns))
    lower = bullet.lower()
    for phrase in DUTY_PHRASES:
        if phrase in lower:
            found.append(f'a duty phrase: "{phrase}"')
    if not re.search(r"\d", bullet) and not any(w in lower for w in OUTCOME_WORDS):
        found.append("no measure and no named outcome")
    if len(bullet) > MAX_CHARACTERS:
        found.append(f"{len(bullet)} characters, more than two lines")
    return found


def verdict(bullet):
    found = problems(bullet)
    if not found:
        return f"passes ({len(bullet)} characters)"
    return f"breaks {len(found)}: " + "; ".join(found)


failed_before = passed_after = broken = 0
for n, (where, rule, before, after) in enumerate(PAIRS, start=1):
    print(f"{n:>2}. {where} (rule: {rule})")
    print(f"    before  {before}")
    print(f"            {verdict(before)}")
    print(f"    after   {after}")
    print(f"            {verdict(after)}")
    broken += len(problems(before))
    failed_before += bool(problems(before))
    passed_after += not problems(after)
print()
print(f"Before: {failed_before} of {len(PAIRS)} bullets break at least one rule, {broken} problems in all.")
print(f"After: {passed_after} of {len(PAIRS)} bullets pass all five rules.")
print()
print("One rewrite with a false number:")
print(f"    {FALSE_NUMBER}")
print(f"    {verdict(FALSE_NUMBER)}: the form is right, and no rule can tell that 18 ms never happened.")
for bullet in MINE:
    print()
    print(f"    yours   {bullet}")
    print(f"            {verdict(bullet)}")

Output

 1. Canteen queue app (rule: a verb and what changed)
    before  Worked on the canteen queue app.
            breaks 3: starts with "Worked", not an action verb; a duty phrase: "worked on"; no measure and no named outcome
    after   Built a token queue for the college canteen in Flask and SQLite, cutting the average lunch-hour wait from 14 to 6 minutes.
            passes (122 characters)
 2. Hostel mess feedback (rule: a result, not a duty)
    before  Responsible for the hostel mess feedback form.
            breaks 3: starts with "Responsible", not an action verb; a duty phrase: "responsible for"; no measure and no named outcome
    after   Designed a two-question mess feedback form that collected 1,860 responses in one term; the committee changed three dishes.
            passes (122 characters)
 3. College fest website (rule: your part, not the team's)
    before  We made the fest website.
            breaks 3: starts with "We", not an action verb; a pronoun: We; no measure and no named outcome
    after   Built the registration and payment pages of the college fest website, used by 1,140 students in its first week.
            passes (111 characters)
 4. Route lookups at Example Logistics (rule: a measure and the method)
    before  Helped with performance improvements on the API.
            breaks 3: starts with "Helped", not an action verb; a duty phrase: "helped with"; no measure and no named outcome
    after   Cut p95 API latency from 420 ms to 180 ms by adding PostgreSQL indexes and a Redis cache for route lookups.
            passes (107 characters)
 5. Shipment events at Example Logistics (rule: the scope)
    before  Involved in building a Kafka consumer.
            breaks 3: starts with "Involved", not an action verb; a duty phrase: "involved in"; no measure and no named outcome
    after   Built a Kafka consumer for 2 million shipment events a day, with idempotent writes and Grafana alerts.
            passes (102 characters)
 6. Billing tests in an internship (rule: a baseline for the number)
    before  Was tasked with writing unit tests.
            breaks 3: starts with "Was", not an action verb; a duty phrase: "tasked with"; no measure and no named outcome
    after   Wrote 140 unit tests for the billing module, raising its line coverage from 48 % to 81 %.
            passes (89 characters)
 7. Invoice export (rule: no pronouns, and an honest estimate)
    before  I automated some reports for the finance team.
            breaks 3: starts with "I", not an action verb; a pronoun: I; no measure and no named outcome
    after   Automated the weekly invoice export with a Python script, saving the finance team about 3 hours a week.
            passes (103 characters)
 8. Library seat booking (rule: a result in place of upkeep)
    before  Duties included maintaining the library seat booking site.
            breaks 3: starts with "Duties", not an action verb; a duty phrase: "duties included"; no measure and no named outcome
    after   Added e-mail reminders to the library seat booking site, reducing no-shows from 31 % to 12 % in one semester.
            passes (109 characters)
 9. Python workshops (rule: what changed for other people)
    before  Helped juniors learn Python.
            breaks 2: starts with "Helped", not an action verb; no measure and no named outcome
    after   Ran six weekend Python workshops for 45 first-year students; 38 of them finished every exercise.
            passes (96 characters)
10. Open-source fix (rule: a named outcome, when there is no number)
    before  Contributed to an open-source CSV library over the summer, where I read the code base, joined the discussions on several issues, worked on a bug about dates and learned a lot about testing and code review.
            breaks 5: starts with "Contributed", not an action verb; a pronoun: I; a duty phrase: "worked on"; no measure and no named outcome; 205 characters, more than two lines

Text longer than 4,000 characters is cut here; run the example yourself to see all of it.

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

All ten weak bullets break at least one rule, 31 problems in all, and all ten rewrites pass. Two rewrites show what to do without a hard number: the open-source fix names an outcome a reader can check, a merged pull request, and the invoice export gives an honest estimate, about 3 hours a week. The last line is the warning. Asha’s latency bullet with 18 ms in place of 180 ms passes too, because a checker reads the form of a bullet, never its truth. Every number has to come from your own records, and the next lesson is about finding them.

Starting with a phrase about your job

Wrong: “Responsible for the release pipeline.” Right: “Cut release time from 3 hours to 25 minutes by scripting the release checklist.” The first describes what the job asked of you; the second says what you made happen, and an interviewer can ask how.

Checkpoint

Check: Results instead of duties

  1. Question 1 of 2 You were on the team that answered support tickets for a college portal. Applying the lesson's three questions, which line results?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Cut the median reply time to portal tickets from 3 days to 1 by writing answers to the 12 most common questions.

    Ask what was different after your work, how you know and what you did. The answers are the X, the Y and the Z of the bullet.

  2. Question 2 of 2 Your fix to an open-source library has no number you could measure. What makes the bullet a result?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An outcome a reader can check, such as the maintainers merging the pull request

    When there is no honest number, name an outcome that a reader could check: merged, adopted, published, won.

Precise verbs, one tense and no pronouns

“Worked on”, “helped with” and “involved in” have one thing in common: they hide what you did. The official lists of action verbs point the other way. Job Bank suggests handled, managed, led, developed, increased, accomplished and leveraged; Europass recommends strong verbs such as managed, developed and increased; MIT suggests built, managed, developed and wrote; and Harvard’s guide sorts its verbs by kind of work, from technical to teaching. The best verb is the one that names your work exactly:

Instead of It hides Say what you did
Worked on what you did Built, migrated, tested, profiled
Helped with how big your part was Wrote, reviewed, fixed, ran
Responsible for whether anything happened Cut, raised, launched, automated
Involved in whether you acted at all Designed, led, presented

Three habits keep a list of bullets readable. Pronouns: Job Bank says not to use “I”, “my” or “me”, MIT says to avoid the first person, and Harvard adds “We”. Tense: use the past tense for finished work, including results you have already reached in your current job, and the present tense only for what you still do: “Run the release checklist for 14 services” next to “Moved 14 services to Kubernetes”. That convention is MySmartCoPilot guidance; whichever you choose, use it all the way down. Voice: Harvard asks for active rather than passive language, because “The cache was added” hides who added it.

Dropping “We” is where the real work starts. “We built the fest website” credits nobody; “Built the registration and payment pages of the college fest website” names your part. Google asks about leadership in the same spirit: how big was the team, and what was the scope of your work? If you led the four people who built it, say “Led a team of four”.

Passive Voice & Writing Style Checker Paste your bullets to find passive sentences that hide who did the work.

Checkpoint

Check: Verbs, tense, pronouns and your own part

  1. Question 1 of 2 A team of five built the fest website. You designed its database schema. Which bullet credits your part accurately?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Designed the PostgreSQL schema of the college fest website, which handled 1,140 registrations in its first week.

    Drop "we", then name your own part with a verb that says exactly what you did. The team's numbers may describe the scale.

  2. Question 2 of 2 In your current job you still run the release checklist, and you finished moving 14 services to Kubernetes last quarter. Which pair follows the lesson's tense convention?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: "Run the release checklist for 14 services" and "Moved 14 services to Kubernetes"

    Past tense for finished work, results in your current job included; present tense only for what you still do. Whichever convention you choose, keep it all the way down.

Short bullets, a few per role, the strongest first

The guides agree on short. MIT advises bullet points of 1 to 2 lines each, the National Careers Service 2 to 3 lines per job, and Job Bank at most 5 to 7 bullet points in any section of a resume. For a tech resume, MySmartCoPilot’s default is three to five bullets for a recent, relevant role, fewer for older or less relevant ones, and at most two printed lines per bullet, about the 200 characters the checker allows.

Order matters as much as length. Within a role, put bullets in order of relevance to the posting, not in the order the work happened, because the first bullet under each job is the one most likely to be read; a first screen, as the first lesson of this track worked out, reaches only the top of page one. The posting’s signals decide what relevant means: its must-have terms and its stress on scale, ownership or on-call work.

The habit that makes this fast is a bullet bank: A private list of every true bullet you could write for a job or a project, each with the record that proves it, from which you choose the few that fit one posting.: every true bullet you could write for a job, each with the record that proves it, kept in one private file. For each posting you choose and order from the bank instead of writing from scratch. Step through Asha choosing four bullets for one posting:

Interactive · stage by stage From a bank of bullets to the four a job shows Step through Asha choosing bullets for one posting. Watch which lines make the page, in what order, and where the rest go.
From a bank of bullets to the four a job shows, step 6 of 6. Showing Bullet bank, Match the posting, Order by relevance, Keep three to five, Two lines at most. In focus: Two lines at most.the rest stay bankedBullet bank9 true lines for this jobMatch the posting5 answer its termsOrder by relevancescale signal firstKeep three to five4 make the pageTwo lines at mosteach under 200 characters

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

Every step as text
  1. The bank. Asha's bullet bank holds nine true lines for her current job, each with the record that proves it: a Kafka consumer, a latency cut, a move to Kubernetes, on-call incidents, an invoice export, a Python upgrade, a test suite, a tech talk and a setup guide.
  2. Match the posting. She reads them against the Example Freight posting. Five answer its terms or signals: Kafka at scale, PostgreSQL and the API, Docker and Kubernetes, on-call work, and Python in the invoice export.
  3. Order by relevance. The posting stresses scale and on-call, so the Kafka consumer leads: 2 million shipment events a day answers the first signal and its alerts the second. The latency cut, with PostgreSQL and the API, comes next.
  4. Keep three to five. Four fit her one page: the consumer, the latency cut, Kubernetes and the 23 incidents. The invoice export is true but the weakest match, so it stays out this time.
  5. The rest stay banked. Nothing is thrown away. The five lines left out wait in the bank, and a posting for a data platform would pick a different four, with the invoice export moving up.
  6. Two lines at most. Each kept bullet fits two printed lines, about 200 characters. The longest of the four, the on-call bullet, has 116 characters, so none of the four needs a third line.

Nothing she left out was deleted: the five lines that waited in the bank are the ones a different posting will pick, and each still has the record that proves it.

Checkpoint

Check: How many bullets, and in what order

  1. Question 1 of 2 You have three jobs: a current, relevant one, a relevant one from two years ago, and an unrelated job from eight years ago. How many bullets does each get under the lesson's default?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Three to five, fewer, and one or none

    MySmartCoPilot's default is three to five bullets for a recent, relevant role and fewer for older or less relevant ones, each at most two printed lines.

  2. Question 2 of 2 Which bullet goes first under a job?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The one most relevant to this posting

    Order bullets by relevance to the posting: the first bullet under each job is the most likely to be read.

Practise: honest bullets from a facts sheet

The lab has two steps. First write the check the form checker lacked, the one that finds numbers your records do not hold. Then rewrite the eight bullets of Kabir, an invented intern, from his facts sheet, so that every bullet passes that check and the five rules of form:

Code lab · 2 steps

Honest bullets from a facts sheet

Write a check that finds the numbers a facts sheet does not hold, then rewrite an intern's eight bullets so that they pass it and the five rules of form.

  1. Find the numbers your records do not hold To do
  2. Rewrite an intern's eight bullets from the facts To do

Exercise · Easy · Python

Find the numbers your records do not hold

A checker can test the form of a bullet but not its truth, except in one way: it can check that every number in the bullet comes from your records. Write unsupported(bullet, facts) in facts_check.py. facts is a list of lines from a facts sheet; the function returns the numbers of bullet that appear in none of those lines, each written as in the bullet, once, in the order they first appear.

  • A number is a run of digits that may have commas between groups of three and one decimal point: 81, 1,860, 3.5. A full stop after it ends a sentence and is not part of it.
  • A number joined to letters, as in p95, Python3 or 4th, is part of a name, not a measure: ignore it.
  • Commas do not change a number, so 1,860 in a bullet is supported by 1860 in the facts, and the other way round.

The sample tests use bullets and facts from this lesson, a false number like the one the checker could not catch, and the edge cases above.

Starter code · facts_check.py

import re


def unsupported(bullet, facts):
    """The numbers of bullet that no line of facts holds, each once, in the order they first appear."""
    # Replace this comment and the line below with the rules of the prompt.
    return []
The sample tests · test_facts_check.py
from facts_check import unsupported

ASHA = [
    "p95 latency of the route-lookup API: 420 ms before, 180 ms after the PostgreSQL indexes and the Redis cache",
    "Kafka consumer: 2 million shipment events a day",
]
MESS = ["Feedback form: 1860 responses in one term; the committee changed 3 dishes"]


def test_every_number_from_the_facts():
    """finds nothing to report when every number is in the facts"""
    bullet = "Cut p95 API latency from 420 ms to 180 ms by adding PostgreSQL indexes and a Redis cache for route lookups."
    assert unsupported(bullet, ASHA) == []


def test_a_false_number():
    """reports the 18 ms that no record holds, the number the form checker passed"""
    bullet = "Cut p95 API latency from 420 ms to 18 ms by adding PostgreSQL indexes and a Redis cache for route lookups."
    assert unsupported(bullet, ASHA) == ["18"]


def test_commas_do_not_change_a_number():
    """matches 1,860 in the bullet with 1860 in the facts, and reports an invented 1,900"""
    assert unsupported("Collected 1,860 responses in one term.", MESS) == []
    assert unsupported("Collected 1,900 responses in one term.", MESS) == ["1,900"]


def test_names_are_not_numbers():
    """ignores p95, Python3 and 4th, which are parts of names"""
    assert unsupported("Ported the p95 report to Python3 for the 4th team.", MESS) == []


def test_decimals_and_full_stops():
    """keeps the decimal point inside a number and the full stop outside it"""
    facts = ["Saved about 3.5 hours a week; coverage rose to 81"]
    assert unsupported("Saved about 3.5 hours a week, raising coverage to 81.", facts) == []
    assert unsupported("Saved about 3.6 hours a week.", facts) == ["3.6"]


def test_each_number_once_in_order():
    """lists each unsupported number once, in the order it first appears"""
    bullet = "Cut the wait from 15 to 5, then to 5 again, for 300 students."
    assert unsupported(bullet, MESS) == ["15", "5", "300"]
A hint

Write one regular expression for a number and use it twice: once on every line of facts, to build a set of the numbers they hold (with the commas removed), and once on the bullet. A lookbehind such as (?<![\w.,]) and a lookahead such as (?!\w) keep p95 and 4th out.

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.

Exercise · Easy · Python

Rewrite an intern's eight bullets from the facts

Kabir, an invented intern, spent ten weeks in the IT team of an invented pharmacy chain. His draft resume has eight weak bullets, in BULLETS in bullets.py, and his facts sheet records what really happened, one line per bullet:

  1. A Python script checks expiry dates in the stock database every night; it flagged 312 expired items in its first month. Before, staff checked the shelves by hand.
  2. The stock report moved from a spreadsheet to a Flask page; it loads in 2 s instead of 40 s.
  3. 64 new unit tests for the billing module; its line coverage rose from 35 % to 72 %.
  4. 17 bugs from the team's tracker fixed in 10 weeks.
  5. Barcode scanning on the receiving screen: receiving a delivery of 200 items took about 50 minutes before and about 20 after, timed on three deliveries.
  6. A setup guide for new interns: the next 2 interns set the project up in 1 day instead of 3.
  7. A talk on the expiry check for 25 pharmacists and store staff at the monthly meeting.
  8. Hand-sent SMS reminders for prescription refills replaced by a scheduled job that sends about 150 a day.

Rewrite the eight strings so that bullet n tells the story of fact n. Each bullet must:

  • start with an action verb in the past tense: a word ending in "ed", or one of the irregular verbs in the tests' IRREGULAR list, but none of the weak openers in WEAK;
  • hold no personal pronoun (I, me, my, we, our, us) and no duty phrase such as "responsible for" or "worked on";
  • hold at least one number, and only numbers that its fact holds: a number the facts do not hold fails, as in the check of the lab's first step;
  • name its own subject (the expiry check, the report, the tests, the bugs, the barcodes, the guide, the talk, the reminders), and fit in 200 characters.

The sample tests check all eight bullets against these rules.

Starter code · bullets.py

# Rewrite each bullet from the fact with the same number in the prompt.
BULLETS = [
    "Responsible for checking expiry dates.",
    "Worked on the stock report.",
    "Helped with testing the billing module.",
    "Fixed bugs.",
    "I added barcode scanning.",
    "Wrote documentation for interns.",
    "Gave a presentation.",
    "Involved in the SMS reminder system.",
]
The sample tests · test_bullets.py
import re

from bullets import BULLETS

FACTS = [
    "A Python script checks expiry dates in the stock database every night; it flagged 312 expired items in its first "
    "month. Before, staff checked the shelves by hand.",
    "The stock report moved from a spreadsheet to a Flask page; it loads in 2 s instead of 40 s.",
    "64 new unit tests for the billing module; its line coverage rose from 35 % to 72 %.",
    "17 bugs from the team's tracker fixed in 10 weeks.",
    "Barcode scanning on the receiving screen: receiving a delivery of 200 items took about 50 minutes before and about "
    "20 after, timed on three deliveries.",
    "A setup guide for new interns: the next 2 interns set the project up in 1 day instead of 3.",
    "A talk on the expiry check for 25 pharmacists and store staff at the monthly meeting.",
    "Hand-sent SMS reminders for prescription refills replaced by a scheduled job that sends about 150 a day.",
]
SUBJECTS = [("expir",), ("report",), ("test",), ("bug",), ("barcode",), ("guide",), ("talk", "present"), ("remind",)]
IRREGULAR = {"built", "cut", "drew", "gave", "led", "made", "ran", "rewrote", "set", "spoke", "taught", "won", "wrote"}
WEAK = {"worked", "helped", "assisted", "participated", "involved", "tasked", "responsible"}
PRONOUNS = {"i", "me", "my", "we", "our", "us"}
DUTY = ("responsible for", "worked on", "helped with", "involved in", "tasked with", "duties included")
NUMBER = re.compile(r"(?<![\w.,])(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?(?!\w)")


def words(bullet):
    return re.findall(r"[A-Za-z][A-Za-z'-]*", bullet)


def numbers(text):
    return [n.replace(",", "") for n in NUMBER.findall(text)]


def test_eight_different_bullets():
    """keeps eight bullets, all different"""
    assert len(BULLETS) == 8
    assert len(set(BULLETS)) == 8


def test_each_starts_with_an_action_verb():
    """starts every bullet with a past-tense action verb that is not a weak opener"""
    for n, bullet in enumerate(BULLETS, start=1):
        first = words(bullet)[0].lower()
        assert first.endswith("ed") or first in IRREGULAR, f"bullet {n} starts with {first!r}"
        assert first not in WEAK, f"bullet {n} starts with the weak opener {first!r}"


def test_no_pronouns_and_no_duty_phrases():
    """leaves out personal pronouns and duty phrases"""
    for n, bullet in enumerate(BULLETS, start=1):
        assert not {w.lower() for w in words(bullet)} & PRONOUNS, f"bullet {n} has a personal pronoun"
        assert not any(p in bullet.lower() for p in DUTY), f"bullet {n} has a duty phrase"


def test_numbers_come_from_the_facts():
    """gives every bullet a number, and only numbers that its own fact holds"""
    for n, (bullet, fact) in enumerate(zip(BULLETS, FACTS), start=1):
        found = numbers(bullet)
        assert found, f"bullet {n} has no number"
        extra = [x for x in found if x not in numbers(fact)]
        assert not extra, f"bullet {n} has numbers that fact {n} does not hold: {extra}"


def test_each_names_its_subject_and_fits_two_lines():
    """names the subject of its fact and stays within 200 characters"""
    for n, (bullet, subject) in enumerate(zip(BULLETS, SUBJECTS), start=1):
        assert any(s in bullet.lower() for s in subject), f"bullet {n} does not name its subject ({subject[0]}…)"
        assert len(bullet) <= 200, f"bullet {n} has {len(bullet)} characters"
A hint

Take each fact in turn and answer three questions in one line: what did Kabir do (the verb), what changed (with the numbers of that fact and no other), and how. Fact 6 becomes, for example, a line that starts "Wrote a setup guide …" and ends with the 1 day and the 3 days.

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.

Then work through questions on the four ideas of this lesson; the one you have answered worst so far comes first:

Practice that adapts to you

Practise this lesson

12 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 12 Which parts does Google's formula, "accomplished [X] as measured by [Y], by doing [Z]", ask a bullet to hold? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 1 of 12

    Answer:

    • What you did to get there
    • What you accomplished
    • How it was measured

    X is the accomplishment, Y the measure and Z the method. MIT's PAR statements and the National Careers Service's STAR method ask for the same action and result.

  2. Question 2 of 12 Your records say the nightly build took 14 minutes before you cached Docker layers and 6 minutes after. Which bullet uses them best?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: Cut the nightly build from 14 to 6 minutes by caching Docker layers.

    Use the numbers your records hold, with the baseline: from 14 to 6 minutes. The method, caching Docker layers, is what turns the claim into something an interviewer can explore.

  3. Question 3 of 12 A posting asks above all for experience with Redis. Your bullet is "Cut p95 API latency from 420 ms to 180 ms by adding PostgreSQL indexes and a Redis cache". What change, if any, helps this reader most?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: Bring the method forward, as in "Added a Redis cache and PostgreSQL indexes that cut p95 API latency from 420 ms to 180 ms"

    Keep all three parts, and move the one the posting cares about most to the front.

  4. Question 4 of 12 Which line is a duty statement rather than a result?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: Responsible for maintaining the library seat booking site.

    Job Bank: your duties are obvious from your job title. A bullet should say what you achieved in the job instead.

  5. Question 5 of 12 "Responsible for on-call." Asking the lesson's three questions, which answers turn it into a result?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: Resolved 23 production incidents as one of six on-call engineers, and wrote the runbook for the three most frequent.

    What was different after your work, how do you know, what did you do? The answers give the bullet its X, Y and Z.

  6. Question 6 of 12 Your internship project, a prototype stock forecaster, never reached production. You tested it on 8 weeks of past sales. What result can you honestly claim?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: Built a stock forecaster prototype that predicted 8 weeks of past sales more closely than the store's rule of thumb, measured on its own test

    A result does not need to be shipped to be real. Say what you built and what your own test measured, and never claim an effect in production that never happened. With your records, name the error measure as well.

  7. Question 7 of 12 Which verb should replace "Helped with" in "Helped with tests for the billing module", if you wrote 40 of them?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: Wrote

    The best verb names your work exactly: "Wrote 40 unit tests for the billing module".

  8. Question 8 of 12 "A Redis cache was added and latency was reduced." What is wrong with it as a bullet?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: The passive voice hides who added the cache, and the line has no measure

    Active verbs make you the subject of every bullet, and a measure makes the change something a reader can judge.

  9. Question 9 of 12 You coordinated four classmates on a project, set the weekly plan and reviewed every pull request, but nobody appointed you. Which opening is honest?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: Coordinated a team of four, setting the weekly plan and reviewing every pull request

    Choose the verb that fits what happened. "Coordinated" and "reviewed" are accurate here; "Led" is fine too when you did lead, without a title you did not have.

  10. Question 10 of 12 Canada's Job Bank suggests limiting each section of a resume to at most how many bullet points?

    Type a number.

    Show the answer: Question 10 of 12

    Answer: 7 bullet points

    Job Bank says 5 to 7 bullet points for each section or sub section. MySmartCoPilot's default for a recent, relevant role is three to five.

  11. Question 11 of 12 A posting stresses scale and on-call work. Asha's bullets include the Kafka consumer for 2 million events a day, with Grafana alerts, and the latency cut. Which leads?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: The Kafka consumer, because its scale and its alerts answer both signals

    The posting's signals decide relevance. In Asha's case the consumer answers scale and on-call at once, so it leads.

  12. Question 12 of 12 You have five years of experience. Your first job, a support role unrelated to the backend roles you apply for, still has six bullets. What does the lesson suggest?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: Cut it to one line or one bullet, and keep the six in your bullet bank

    Space goes where the evidence for this posting is. The job stays, dated, in a line; its bullets wait in the bank.

Interview questions

Warm-up (fresher to mid-level): What makes a resume bullet strong?

It says what changed, by how much and how I did it, in one or two lines. “Cut p95 API latency from 420 ms to 180 ms by adding PostgreSQL indexes and a Redis cache” starts with a verb for my action, gives the change with its baseline and unit, and names the method, which is what an interviewer asks about first. A duty such as “Responsible for the API” has none of the three. Two more things make a bullet strong: it is about my part, not the team’s, and every number in it comes from a record I could show. When there is no number, I name an outcome someone can check, such as a merged pull request.

An interviewer may press on that answer. Pick the reply you would give; each choice says what it gets right or misses:

Checkpoint

Interview round: two follow-up questions

  1. Question 1 of 2 The interviewer asks: "Your bullet says you improved performance by 60 %. How did you measure that?" You estimated it from memory. What do you say?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: That it was my estimate, what I based it on, and what I would measure today to be sure

    Every number on a resume is a question waiting to be asked. Say where it came from; and before the next application, replace the estimate with a measurement or label it as an estimate.

  2. Question 2 of 2 The interviewer asks: "Your bullet says 'Built the fest website'. Did you build it alone?" Four of you built it, and you wrote the payment pages. What is the best reply?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No, four of us built it; I wrote the payment pages, and I will say so on the resume from now on

    A bullet names your part with a precise verb. When an interviewer finds a line that claims too much, correcting it plainly is the answer that keeps the rest of the resume credible.

Two questions of this module’s bank put the rules to work on harder cases:

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

  1. Fresher to mid

    Your team of five built a college fest website that 1,140 students used, and you wrote its registration and payment pages. How do you write the bullet so that it credits your part without hiding the team, and what do you say when the interviewer asks who did what?

    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 senior

    You have nine true bullets for your current job and room for four. The posting stresses scale and on-call work. How do you choose and order the four, what happens to the other five, and how would the choice change for another posting?

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

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

  1. What shape does Google's hiring page ask a resume accomplishment to take?

    Accomplished X, as measured by Y, by doing Z: the result, the measure that shows it, and the work that produced it.

  2. Which parts must a strong bullet keep, whatever their order?

    A verb for your action, the change with its measure, and the method. The order may bend, for instance to put a technology the posting asks for before the numbers, but without the change and its measure the bullet is a duty again.

  3. Which three questions turn a duty into a result?

    What was different after your work, how do you know, and what did you do to make it so? The answers supply the result, the measure and the method.

  4. What can a bullet offer when there is no hard number?

    An outcome someone can check, such as a pull request the maintainers merged, or an honest estimate labelled as one, such as about 3 hours a week saved.

  5. What can a bullet checker never tell you?

    Whether the bullet is true. It reads form only: a verb first, no pronoun, a measure, a sensible length. A bullet with a wrong figure passes just as easily, so every number has to come from your own records.

  6. Which habits keep a list of bullets readable?

    A precise action verb instead of “worked on” or “helped with”; no “I”, “my”, “me” or “we”; past tense for finished work and present tense only for what you still do, used consistently; and active voice, which shows who did the work.

  7. How many bullets does a recent, relevant role get, and in what order?

    Three to five, none longer than two printed lines, and fewer for older or less relevant roles. Order them by relevance to the posting, because the first bullet under a job is the one most likely to be read.

  8. What is a bullet bank, and why keep one?

    A private file holding every true bullet you could write for a job, each next to the record behind it. For each posting you pick and order from it; the lines you leave out wait for another posting.

Key takeaways

  • A strong bullet says what changed, by how much and how you did it: Google’s accomplished X, measured by Y, by doing Z.
  • A duty describes the job; it becomes a result when you answer what was different after your work, how you know, and what you did.
  • Start with a precise action verb, keep one tense convention and no personal pronouns, and name your own part of team work.
  • Give a recent, relevant role three to five bullets of at most two lines, ordered by relevance to the posting, and keep the rest in a bullet bank.
  • A checker can test the form of a bullet, never its truth, so every number must come from your own records.

Terms in this lesson

bullet bank
A private list of every true bullet you could write for a job or a project, each with the record that proves it, from which you choose the few that fit one posting.
STAR method
A way to describe one example of your work in four parts: the situation, the task, the action you took and the result. The UK's National Careers Service suggests it for CVs, application forms and interviews.

References

Related tools

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.