Resume & portfolio
The filter before the interview: a resume that survives the 6-second scan and the ATS, and a portfolio that proves you can build — with runnable checkers for your own materials.
Before interviews there's a filter: a resume a recruiter scans in ~6 seconds and an ATS (applicant tracking system) that keyword-matches you to the role, plus a portfolio (your GitHub + projects from this course) that proves you can actually build. This chapter makes both strong — with a runnable checker you can point at your own resume.
The words you'll hear (in plain terms):
| Term | What it actually means |
|---|---|
| ATS | software that scans resumes for keywords before a human sees them. |
| portfolio | public proof of skill: GitHub repos, deployed projects, writeups. |
| impact bullet | a resume line showing result + how ("cut latency 40% by adding caching"). |
| keyword match | how well your resume's terms overlap the job description. |
| signal | evidence an employer reads as "can do the job". |
What you need before starting:
- Having built a few projects here (the capstones are portfolio-ready).
- A draft resume + your GitHub to apply this to.
- The Python checker runs offline — paste your own text in.
New to the topic? Read this box, then take the chapters in order — each section is tagged essential → expert so you always know the depth you're at.
Learning objectives
- Write impact-driven resume bullets that survive the 6-second scan.
- Pass the ATS keyword filter without stuffing.
- Turn course projects into portfolio proof.
- Curate a portfolio like a hiring manager reads it.
code/cr1-resume-portfolio/ — run them against your own resume, stories, and offers.1 · The 6-second scan & impact bullets essential
A recruiter skims; your bullets must show result + how, not duties. Formula: action verb → what → measurable impact → how. "Responsible for testing" is a duty; "Cut prod incidents 30% by adding a CI test gate" is impact.
bullet_score.pyimport re
def score_bullet(bullet):
score, notes = 0, []
if re.match(r"^(Built|Led|Cut|Shipped|Designed|Automated|Reduced|Improved|Launched)", bullet):
score += 1
else: notes.append("start with a strong action verb")
if re.search(r"\d", bullet): # has a number/metric
score += 1
else: notes.append("add a measurable result (%, time, count)")
if re.search(r"\bby\b|using|with", bullet): # explains HOW
score += 1
else: notes.append("say HOW you achieved it")
return score, notes
for b in ["Responsible for the test suite",
"Cut deploy failures 30% by adding a CI test gate with pytest"]:
s, n = score_bullet(b)
print(f"[{s}/3] {b}")
for note in n: print(" -", note)
[0/3] Responsible for the test suite
- start with a strong action verb
- add a measurable result (%, time, count)
- say HOW you achieved it
[3/3] Cut deploy failures 30% by adding a CI test gate with pytest
This little program grades one resume bullet (a single line on your resume) out of 3, and tells you what's missing. It captures the whole rule of a good bullet: it should start with a strong action verb, contain a number, and explain how you did it. You paste your own bullets in at the bottom.
import rebrings in Python's regular-expression tool — a way to search text for patterns.score_bullet(bullet)is the reusable checker; it starts thescoreat 0 and an emptynoteslist for advice.- Check 1 (verb):
re.match(r"^(Built|Led|Cut|...)", bullet)asks "does the bullet start with one of these power verbs?" The^means "at the very beginning". If yes,score += 1; if not, we append the advice "start with a strong action verb". - Check 2 (number):
re.search(r"\d", bullet)looks anywhere in the line for a digit (\d= any 0–9). A number is what turns a claim into evidence ("30%", "5 services"). - Check 3 (how): it searches for the words
by,usingorwith— the tell-tale that you explained the method, not just the result. - The
for b in [...]loop runs two example bullets through the checker and prints the score plus each note, so you can see a 0/3 and a 3/3 side by side.
What the output means: A weak line ("Responsible for the test suite") scores [0/3] and lists all three fixes; a strong line scores [3/3] with no notes. The number in brackets is how many of the three rules the bullet passed.
Try this: Paste one of your own resume lines into the list and run it. If it scores below 3, rewrite it using the missing pieces — add a verb, a number, or a "by …" — until it hits 3/3.
2 · Passing the ATS essential
Many resumes are auto-filtered before a human reads them. The ATS matches your resume's terms against the job description. Don't stuff — but do mirror the real skills you have using the words the posting uses.
ats_match.pydef keyword_match(resume, jd, must_haves):
r = resume.lower()
present = [k for k in must_haves if k.lower() in r]
missing = [k for k in must_haves if k.lower() not in r]
pct = round(100 * len(present) / len(must_haves))
return pct, present, missing
resume = "Built LLM apps with Python and FastAPI. Wrote pytest suites. Deployed with Docker."
must = ["Python", "Docker", "pytest", "Kubernetes", "CI/CD", "FastAPI"]
pct, have, miss = keyword_match(resume, "", must)
print(f"match: {pct}%")
print("have:", have)
print("missing (add if TRUE for you):", miss)
match: 67%
have: ['Python', 'Docker', 'pytest', 'FastAPI']
missing (add if TRUE for you): ['Kubernetes', 'CI/CD']
This checks how well your resume matches a job posting — the same idea an ATS (the software that filters resumes before a human reads them) uses. You give it your resume text, the job description, and a list of must-have skills; it tells you your match percentage and which skills are missing.
r = resume.lower()makes a lower-case copy of your resume so the comparison ignores capitalisation ("Python" and "python" count the same).present = [k for k in must_haves if k.lower() in r]is a list comprehension: it walks through every required skill and keeps the ones whose text appears in your resume.missingkeeps the ones that do not appear.pct = round(100 * len(present) / len(must_haves))turns "4 of 6 found" into a percentage (67%).len(...)just counts how many items are in a list.- The bottom lines feed in a sample resume and a list of six required skills, then print the match percentage, the skills you have, and the ones you're missing.
What the output means: match: 67% means the resume mentioned 4 of the 6 required skills; the missing list (Kubernetes, CI/CD) is what you'd add — only if you genuinely have it.
Try this: Replace resume with your own resume text and must with the real must-haves from a job posting. Aim to close the gaps by learning the skill, not by faking the keyword.
3 · Turn course projects into portfolio proof intermediate
Your capstones here (the doc-intelligence pipeline, RAG platform, tested/deployed app) are portfolio-grade. What makes them count: a clear README (what/why/how-to-run), a live demo or screenshots, and the engineering signals — tests, CI, a clean commit history (all the DF/TQ/CD sections).
| Weak portfolio | Strong portfolio |
|---|---|
| tutorial clones | projects that solve a real problem |
| no README / setup | clear README + one-command run |
| "it works locally" | deployed / demo link + tests + CI |
| one giant commit | readable history + a real PR or two |
4 · Advanced — a portfolio a hiring manager reads advanced
Managers skim your GitHub like your resume. Pin 3 projects that show range: one that proves depth (the RAG platform), one that proves breadth (an end-to-end app with tests+deploy), one that shows initiative (something you chose). Each with a README that leads with impact.
portfolio_check.pydef portfolio_ready(project):
checks = {
"has_readme": project.get("readme", False),
"has_tests": project.get("tests", False),
"deployed_or_demo": project.get("demo", False),
"clear_commits": project.get("clean_history", False),
}
ready = sum(checks.values())
verdict = "portfolio-ready" if ready >= 3 else "needs work"
return verdict, [k for k, v in checks.items() if not v]
v, gaps = portfolio_ready({"readme": True, "tests": True, "demo": False, "clean_history": True})
print(v, "| improve:", gaps)
portfolio-ready | improve: ['deployed_or_demo']
This rates whether one of your projects is portfolio-ready — good enough to show a hiring manager. It scores four signals employers look for and calls a project ready once it passes at least three of them.
projectis a small dictionary describing one project — a set of yes/no facts likereadme,tests,demo,clean_history.project.get("readme", False)reads one fact, defaulting toFalse("no") if it wasn't listed.- The
checksdictionary gathers the four True/False answers: has a README, has tests, is deployed or has a demo, and has a clean commit history. ready = sum(checks.values())counts how many are True (in Python,Truecounts as 1).verdict = "portfolio-ready" if ready >= 3 else "needs work"gives the pass/fail using that count.- The
returnhands back the verdict plus the list of failed checks ([k for k, v in checks.items() if not v]) so you know exactly what to improve.
What the output means: portfolio-ready | improve: ['deployed_or_demo'] — the project passed 3 of 4 signals, so it's ready, and the one thing that would make it stronger is a live demo.
Try this: Fill the dictionary in with the true state of one of your own repos. If the verdict is "needs work", tackle the items in the improve list one at a time.
5 · Professional — tailor per role professional
One generic resume underperforms. Keep a master resume, then tailor a version per role: reorder bullets to match the JD's priorities, adjust the summary, mirror the must-have keywords. 15 minutes per application dramatically lifts callback rates.
6 · Tech-lead — your narrative & brand tech-lead
At senior/lead level, hiring is about a narrative: what you're known for and where you're going. Beyond the resume — a focused GitHub, a few writeups (blog/README deep-dives), talks or OSS contributions. You're not listing skills; you're demonstrating judgment and impact at scale.
Exercise CR1.1 — Make your materials pass
Context: The chapter's three tools only help if you run them on your own materials and act on the scores — rewriting bullets, closing real gaps, and getting projects to a hiring bar.
Your task: Run the bullet scorer over every line of your resume and rewrite any scoring below 3; run the ATS matcher against a real posting's must-haves and close true gaps; pick 3 course projects, run the portfolio check, and get each to portfolio-ready.
Requirements:
- Rewrite each sub-3 bullet with a strong verb, a number, and a "by..." clause
- Close ATS gaps only with keywords that are genuinely true (defensible in interview)
- Get each of three projects to at least 3 of 4 signals: README, tests, demo/deploy, clean history
- Iterate — re-run each tool after editing
- Non-code: the deliverable is improved materials, not a program
💡 Hint: Treat the scores as a to-do list, not a grade — the value is in the rewrite each low score forces.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: A resume gets a six-second scan, and "Responsible for" lines say nothing about impact. The impact-bullet shape — action verb + what + measurable result — is what survives that scan.
Your task: Rewrite this weak resume line into an impact bullet using the action verb + what + measurable result shape: "Responsible for building a chatbot for the support team."
Requirements:
- Open with a strong action verb, not "Responsible for"
- Name the concrete artifact and the tech stack that built it
- Include a measurable result (a percentage, a before/after, a time)
- Quantify scope where it adds credibility (e.g. "across 12k monthly tickets")
- Never invent metrics — use only numbers you could defend
💡 Hint: The formula is verb → what → number; if a line has no number, it's still a duty statement, not an impact bullet.
Show solution
Weak: starts with “Responsible for”, names a duty, has no result.
Rewritten:
Built a retrieval-augmented support chatbot (Python, FastAPI, Claude API) that deflected 38% of tier-1 tickets, cutting mean first-response time from 6h to under 2m.
Why it works: strong verb (Built) → concrete artifact + stack → a measurable business result (38% deflection, 6h→2m). If you lack a real metric, quantify scope instead: “… across 12k monthly tickets.” Never invent numbers you can’t defend in an interview.
Context: Before a human reads your resume, an ATS matches it literally against the posting's phrases — but human readers discount keyword lists. A good bullet satisfies both by using the keywords in context.
Your task: You're applying to a role whose posting stresses "RAG pipelines, vector databases, evaluation". Re-target your generic bullet so an ATS keyword scan and a 6-second human scan both succeed — without keyword stuffing.
Requirements:
- Map your real work to the posting's exact keyword phrases
- Place each keyword in concrete context, not a bare Skills list
- Show the pipeline/result so a human reads substance, not stuffing
- Include a measurable outcome (e.g. a regression caught before release)
- Only claim keywords that are genuinely true of your work
💡 Hint: Weave the posting's phrases into a sentence that also carries a result — the ATS sees the words, the human sees the work.
Show solution
Generic: “Worked on an AI search feature.”
ATS-aligned:
Designed a RAG pipeline (chunking → embeddings → vector database retrieval → Claude synthesis) and built an offline evaluation harness (faithfulness + citation metrics) that caught a 9% regression before release.
The three posting keywords appear in context, mapped to real work — that beats a “Skills: RAG, vector DB, eval” list an ATS can read but a human discounts. Use the exact noun phrases from the posting; ATS matching is literal.
Context: A hiring manager spends about 90 seconds on a project. The top of the README has to convey the problem, your role, the result, and how to run it inside that window.
Your task: Draft the top-of-README block for a course project so a hiring manager grasps the problem, your role, the result, and how to run it in ~90 seconds.
Requirements:
- Lead with a one-line pitch and why the problem matters
- State what you built, honestly bounding any shared/borrowed work
- Give quantified results (deflection %, eval results, latency)
- Provide a one-command way to run it (e.g.
docker compose up) - Name the tech stack and link an architecture reference
- Order it problem → your role → result → how to run
💡 Hint: Explicitly naming what was shared reads as senior, not diminishing — it signals you know the difference between your work and the team's.
Show solution
# Support Triage Agent
**One line:** classifies incoming support tickets and drafts grounded replies,
escalating low-confidence cases to a human.
**Why it matters:** tier-1 volume was drowning a 4-person team; this deflects
routine questions and routes the rest.
**What I built:** the classifier + confidence gate + RAG answer path + the eval
harness. (Retrieval infra was a shared library.)
**Results:** 38% deflection on a 500-ticket eval set; 0 hallucinated policy claims
(citations enforced); p95 latency 2.1s.
**Run it:** `docker compose up` then open localhost:8000 — seeded demo data included.
**Stack:** Python, FastAPI, Claude API, pgvector. **Architecture:** see diagram below.
The order matters: problem → your role → result → how to run. “What I built” is scoped honestly (you name what was shared), which reads as senior, not as diminishing your work.
Context: Before you publish, grade your flagship project on the dimensions a hiring manager actually weighs — because the weakest dimension is exactly where an interviewer will probe.
Your task: Fill a rubric for one project across the dimensions that matter and identify the single weakest dimension to fix first.
Requirements:
- Score dimensions like problem framing, runs-in-under-5-minutes, evaluation, scope honesty, and failure handling
- Grade each 0 (missing) / 1 (meets bar) / 2 (above bar)
- Meets-bar examples: README states the problem, setup works, some tests exist, happy path runs
- Above-bar examples: named tradeoffs, one-command demo with no secrets, an offline eval catching regressions, failure/bad-input handling
- Fix the lowest-scoring dimension first (often evaluation or failure handling)
💡 Hint: Evaluation and failure handling are where most projects only show the happy path — and that's precisely where interviewers push.
Show solution
| Dimension | Meets the bar | Above the bar |
|---|---|---|
| Problem framing | README states problem + who it’s for | Names the tradeoff and why this approach |
| Runs in <5 min | Clear setup, seeded data | One command (docker compose up), no secrets needed for demo |
| Evaluation | Some tests exist | Offline eval set + a metric that would catch a regression |
| Scope honesty | Says what you built | Distinguishes your work from shared/borrowed code |
| Failure handling | Happy path works | Shows what happens on bad input / model error |
Score each 0 (missing) / 1 (meets) / 2 (above). Fix the lowest first — usually Evaluation or Failure handling, since most course projects only show the happy path, and that is exactly where an interviewer probes.
Context: One project can be told two ways. Tailoring the same work to a product-focused role and an infrastructure-focused role — by selection and emphasis, never fabrication — is a professional skill.
Your task: Same project, two postings: (A) a product-focused "AI Engineer" role and (B) an infrastructure-focused "ML Platform" role. Write the same project as two bullets, each emphasising what that role cares about.
Requirements:
- Version A emphasises user outcome and quality (deflection, citation enforcement, hallucinations held at zero)
- Version B emphasises systems, reliability and cost (async serving, cache reuse cutting spend, p95 latency under load)
- Both describe the same project truthfully
- The difference is selection and emphasis, not new facts
- Each bullet still carries a measurable result
💡 Hint: One truth, two lenses — you're choosing which real facts to foreground, never inventing role-specific ones.
Show solution
Shared project: the support triage agent.
Bullet for (A) AI Engineer — emphasize user outcome + quality:
Shipped a support-triage agent that deflected 38% of tickets; designed the confidence gate and citation enforcement that kept hallucinated policy claims at zero on a 500-case eval set.
Bullet for (B) ML Platform — emphasize systems + reliability + cost:
Built the serving path for a support-triage agent: async FastAPI, prompt-cache reuse cutting token spend ~45%, and p95 latency held at 2.1s under load with a rate-limit backoff + retry layer.
Notice: one truth, two lenses. You are not inventing new facts — you are foregrounding the facet each reader scores. Tailoring is selection and emphasis, never fabrication.
Context: Senior candidates are remembered by a narrative, not a skills list. A 2–3 sentence positioning statement gives interviewers a hook and steers the conversation to your strongest ground.
Your task: Craft a 2–3 sentence positioning statement (your "tell me about yourself" and LinkedIn headline) with a theme, evidence, and a direction — then critique a weak version.
Requirements:
- Name a clear theme (what you're known for), not a tech list
- Back it with one concrete piece of evidence (a specific result)
- State a direction (what you want to own next)
- Critique a weak version that is just a skills list with no theme or evidence
- Keep it to 2–3 sentences and memorable
💡 Hint: Theme → evidence → direction: the skills-list version is forgettable precisely because it has none of the three.
Show solution
Weak (a list): “I’m a Python developer with experience in AI, RAG, FastAPI, Docker, and AWS looking for a new opportunity.” — no theme, no evidence, forgettable.
Strong (a narrative):
I build LLM systems that teams can actually trust in production — my work centers on the un-sexy parts: evaluation, grounding, and graceful failure. Most recently I built a support-triage agent whose citation-enforcement and confidence gate held hallucinated claims at zero on a 500-case eval. I’m looking to own the reliability side of a customer-facing AI product.
The pattern is theme (trustworthy LLM systems) → evidence (the eval result) → direction (what you want next). It gives the interviewer a hook and tells them what to ask about — you steer the conversation onto your strongest ground.
✓ Checkpoint — you can move on when you can…
- Write impact bullets that pass the 6-second scan.
- Match a resume to a JD without keyword stuffing.
- Turn course projects into portfolio proof.
- Curate a portfolio and craft a senior narrative.
Knowledge check check yourself
What three elements make a strong impact bullet, versus a duty statement like "Responsible for testing"?
Show answer
Why should you mirror a job description's keywords on your resume, and what's the rule that keeps this honest?