Your Portfolio, and How to Ask a Question That Gets Answered

Show the work, and make it easy to help you

Advanced 13 min

In this lesson

Everything so far has been about making code work. This lesson is about the two moments where other people are involved: showing them what you built, and asking them for help.

Both are skills, both are learnable, and both are done badly by default.

Explain it like I’m 5

A portfolio is a small museum of what you can build. A good question is one that hands over every clue the answerer needs.

Three finished projects beat thirty tutorials

The thing anyone looking at your work wants to know is whether you can finish something. Nothing else in a portfolio matters as much.

Which means the selection rules are short:

  • Finished beats ambitious. A small tool that works beats a half-built framework, every time.
  • Yours beats followed. A tutorial project is fine if you changed it and the README says what you changed and why.
  • Different beats similar. Three near-identical to-do apps show one skill. A CLI tool, a data analysis, and a small web app show three.
  • Solving your own problem beats an exercise. It gives you something to say about it, and you will have made real decisions.

Three is plenty. Nobody reads the fourth.

The README is the project, as far as a visitor is concerned

Most people will read your README and never open a source file. If it does not say what the thing does in the first two lines, they leave.

A good README answers five questions in order, and nothing else is required:

Example · README.md
# tidyup

Sorts a messy downloads folder into dated subfolders. I wrote it because
mine had 4,000 files in it.

![tests](https://github.com/ada/tidyup/actions/workflows/tests.yml/badge.svg)

## What it does

Groups files by the month they were modified, moves them into `2026-07/`
style folders, and never overwrites: a clash becomes `report (2).pdf`.

## Install

    pip install git+https://github.com/ada/tidyup

## Use it

    tidyup ~/Downloads --dry-run     # show what would happen
    tidyup ~/Downloads               # actually do it

    sorted 84 files into 6 dated folders

## How it works

A single pass with `pathlib`, grouping by `st_mtime`. The move and the
name-clash logic are separated so both can be tested without real files.

## What I would add next

Undo. Every move is currently one-way, which is the main thing stopping
me from using it without `--dry-run` first.
Short, specific, and it shows the actual output.

Check what you are about to make public

Before anything goes on the internet (a repository, a screenshot, a pasted traceback), check it for things that should not.

  • API keys and tokens, including in the git history. Deleting a key in a later commit does not remove it; the old commit is still there.
  • Passwords and connection strings, which love to hide in a settings.py or a notebook cell.
  • Real personal data in sample files. Swap in fake names.
  • Absolute paths/Users/ada/... tells strangers your full name.
  • Anything in a screenshot: browser tabs, notifications, the file tree.

The habit that prevents all of it is the one from Unit 7: keys live in environment variables and a .env file, and .env is in .gitignore from the first commit. Then there is nothing to remember later.

Example
import re

# What you were about to paste into a public forum.
SNIPPET = """
import requests
API_KEY = "sk_live_9f2Ab7QxT1kZ"
DB = "postgres://ada:[email protected]:5432/sales"
response = requests.get(URL, headers={"Authorization": "Bearer ghp_ZZt4mQ1"})
"""

PATTERNS = [
    (r'(sk_live_|ghp_)[A-Za-z0-9]+', r'\1REDACTED'),
    (r'(postgres://[^:]+:)[^@]+(@)', r'\1REDACTED\2'),
]

safe = SNIPPET
for pattern, replacement in PATTERNS:
    safe = re.sub(pattern, replacement, safe)

print(safe.strip())
Output
import requests
API_KEY = "sk_live_REDACTED"
DB = "postgres://ada:[email protected]:5432/sales"
response = requests.get(URL, headers={"Authorization": "Bearer ghp_REDACTED"})
Thirty seconds of regex before pasting, using the re module from Unit 8.

A question that gets answered

People answer questions that are easy to answer. That is not cynicism, it is arithmetic: a stranger has two minutes, and the question that fits in two minutes gets them.

So the difference between a question that gets three replies and one that gets none is almost entirely about how much work you did first.

  • What you are trying to do — the goal, not just the error. Otherwise you get help with the wrong approach.
  • The smallest code that reproduces it — the shrinking step from the debugging lesson, doing a second job.
  • The full traceback, as text, not a screenshot. People need to search it.
  • What you expected and what happened.
  • What you already tried, so nobody repeats it.
  • Versions, when a library is involved.
Example
BAD
---
Subject: pandas not working

my code doesnt work, i get an error. anyone know why?
[screenshot of a phone photo of a monitor]


GOOD
----
Subject: KeyError on a column that df.columns says exists

I am reading a CSV exported from our billing system and selecting one
column. It raises KeyError even though the name is printed by
df.columns.

    import pandas as pd
    df = pd.read_csv("sales.csv")
    print(df.columns.tolist())
    # ['region', 'total ']
    print(df["total"])
    # KeyError: 'total'

Expected: the total column. Got: KeyError.
Tried: reading with sep="," explicitly, and checking for a BOM.
pandas 2.2.0, Python 3.12, macOS.
The second one is answerable in ten seconds. The first is unanswerable at all.

Score your own draft before posting it. A help request needs all five parts; write missing() to report which are still empty, and ready() to say whether it is worth posting. The draft below has the code and the traceback and is still missing the two that matter most.

REQUIRED = ["goal", "code", "traceback", "expected", "tried"]

DRAFT = {
    "goal": "",
    "code": 'df = pd.read_csv("sales.csv")\nprint(df["total"])',
    "traceback": "KeyError: \'total\'",
    "expected": "",
    "tried": "reading with sep=\',\' explicitly",
}

def missing(draft):
    """Which required parts are empty or absent?"""
    return []      # TODO

def ready(draft):
    """Is this worth posting yet?"""
    return False   # TODO: build this on top of missing()

print("missing:", missing(DRAFT))
print("ready to post:", ready(DRAFT))

DRAFT["goal"] = "select one column from a CSV export"
DRAFT["expected"] = "the total column; got KeyError"
print("after filling those in:", ready(DRAFT))

Contributing, and what comes after

Open source is the least intimidating it has ever been, and the way in is not code.

Documentation is the standard first contribution for a reason: you are reading the docs as a newcomer right now, which makes you the only person who can see what is missing. A fixed typo, a clarified sentence, or an example that was absent is a genuine contribution and gets you through the pull-request process once, on something low-stakes.

After that, look for issues labeled good first issue, read CONTRIBUTING.md before writing anything, and start with a project you actually use: you will understand the problem, and you will care whether the fix lands.

As for where this leads: Python turns up in automation, data analysis, back-end web work, scientific computing, testing, and operations. The most common route into any of them is not a certificate. It is a handful of finished projects, a README that explains them, and being able to talk about a decision you made and why.

Common mistake: Listing tutorial projects without saying what you changed

Why it happens:

It is a real project and you did build it.

How to fix it:

A reader cannot tell your work from the tutorial's. Add a line saying what you added, changed or fixed. That line is the whole value of the entry.

Common mistake: Posting an error with no code and no traceback

Why it happens:

The error message feels like the important part.

How to fix it:

Nobody can reproduce a message. Include the smallest failing code and the full traceback as text, and you will usually get an answer within the hour.

Common mistake: Screenshotting a traceback instead of copying it

Why it happens:

Screenshots are quicker to take.

How to fix it:

They cannot be searched, quoted, or read on a phone. Paste the text in a code block.

Common mistake: Sharing secrets in code or screenshots

Why it happens:

The key was in the file you were debugging, so it comes along.

How to fix it:

Redact before posting, and check the whole screenshot, not just the terminal. Treat anything that was public for even a moment as compromised and rotate it.

Common mistake: Waiting until a project is perfect before sharing it

Why it happens:

You can see every rough edge and assume everyone else will too.

How to fix it:

Nothing is ever finished. Ship it with a “what I would add next” section, which turns the rough edges into evidence of judgment.

What belongs in a README?

What makes a question easy to answer?

What must you remove before sharing code publicly?

What is the most common first open-source contribution?

Mini exercise (hard)

Write the check you run before pasting anything into a public forum. audit(text) returns a sorted list of the kinds of secret it found: "api key" for anything starting sk_live_ or ghp_, "password" for a database URL with credentials in it, and "home path" for an absolute /Users/<name> or /home/<name> path. Then redact(text) returns the text with all three masked.

Take the wheel. Complete the code, hit Run, and check your output right here.

import re

SNIPPET = """API_KEY = "sk_live_9f2Ab7QxT1kZ"
DB = "postgres://ada:[email protected]:5432/sales"
LOG = "/Users/ada/projects/tidyup/run.log"
"""

CLEAN = 'response = requests.get(URL, timeout=10)'

# TODO: (label, pattern, replacement) - keep the recognizable prefix with \\1
RULES = []

def audit(text):
    """Sorted labels for every kind of secret found."""
    return []      # TODO

def redact(text):
    """The same text with every match masked."""
    return text    # TODO

print("found:", audit(SNIPPET))
print("clean:", audit(CLEAN))
print()
print(redact(SNIPPET).strip())

What to learn next

You learned what to show and how to show it: three finished projects beat thirty tutorials, a README answers five questions in order and includes real output, and “what I would add next” reads as judgment rather than as an unfinished job. You also learned to ask a question that gets answered (goal, smallest example, full traceback) and to redact before you paste, which you then wrote the code for.

Everything is in place for a project of your own. The Unit 15 Project is the plan: three stories, a definition of done written before you start, and milestones that each end in something that runs.