Module 10 · Capstone - Real-World Projects ⏱ 19 min

Project Specifications & Planning

By the end of this lesson you will be able to:
  • Break a project into testable milestones with clear inputs and outputs
  • Choose appropriate data structures based on how the data will be accessed
  • Write function signatures before bodies so the plan becomes executable tests

Every project that goes wrong does so in the same way: someone opens an editor and starts typing before they know what the pieces are. Hours later the code is a tangle, the tests are impossible to write, and a simple feature turns out to need a rewrite from the ground up.

A specification is a written plan for what the program must do, and a milestone is a slice of that plan that can be built and tested on its own. Professional developers write the spec first because it is far cheaper to change a sentence than to change a hundred lines of code. The spec does not need to be long; it needs to be clear enough that someone else could read it and know what done looks like.

In this lesson you will learn to break a problem down, pick data structures that match the shape of the data, and write function signatures that act as a contract between the plan and the code.

flowchart LR
  P["Problem"] --> D["Decompose"]
  D --> M["Milestones"]
  M --> S["Data structures"]
  S --> F["Function signatures"]
  F --> B["Build + test"]
  style D fill:#e0900b,color:#fff
  style S fill:#e0900b,color:#fff
  style F fill:#e0900b,color:#fff
The planning pipeline: decompose the problem, choose structures, write signatures, then build.

Milestones: the art of the small slice

A good milestone has three properties. It is testable — you can prove it works without waiting for the rest of the project. It is independent — it does not need tomorrow's code to run today. And it is visible — a human can look at the result and say yes, that part is done.

Bad milestone: Build the app. That is the whole project, not a slice. You cannot know when it is finished because the edges are fuzzy and the definition keeps shifting.

Good milestone: Parse a CSV string into an array of objects. It has a clear input, a clear output, and you can write a test for it right now. You will know it is done when the test passes.

Start every project by listing the milestones in dependency order. If milestone B needs the output of milestone A, A must come first. This order becomes your roadmap and stops you from building the roof before the walls.

A plan object tracks milestones and tells you what to build next.
const plan = {
  project: "Expense Tracker",
  milestones: [
    { name: "Store a transaction", done: false },
    { name: "Sum by category", done: false },
    { name: "Build summary report", done: false }
  ]
};

function nextUp(plan) {
  return plan.milestones.find(m => !m.done)?.name ?? "All done!";
}

console.log(nextUp(plan));
plan.milestones[0].done = true;
console.log(nextUp(plan));

Pick the data structure before the algorithm

The second planning decision is how to hold your information. Two structures cover most beginner projects: the array and the object. The choice is not style; it is determined by how you will look the data up later.

Use an array when order matters and you will mostly loop through everything. Use an object when you need to look something up by name and order does not matter.

If you are building a tally, an object is natural because the word is the key. If you are building a log, an array is natural because the entries arrive in sequence. Choosing wrong does not break the program, but it makes every later function harder to write. A log stored as an object loses order; a tally stored as an array forces you to scan the entire list every time you want one count.

flowchart TD
  Q{Lookup by name?} -- Yes --> O[Object]
  Q -- No --> A2{Order matters?}
  A2 -- Yes --> A[Array]
  A2 -- No --> O2[Object or Array]
  style O fill:#e0900b,color:#fff
  style A fill:#e0900b,color:#fff
Choose an object for named lookups; choose an array for ordered sequences.

Function signatures are contracts

Before you write the body, write the signature: the name, the parameters, and what it returns. A signature is a promise. function totalForCategory(expenses, category) promises that if you hand it an array of expenses and a category string, it will hand back a number. That promise is what makes the function reusable across different parts of your program.

Writing signatures first forces you to decide what each milestone actually produces. If you cannot describe the return value in one sentence, the milestone is too big and should be split. Once the signatures are written, the bodies become almost fill-in-the-blank — and the signatures themselves become the outline of your test file. A file full of empty functions with clear names is not failure; it is a plan that compiles.

Signatures first, bodies later. You can wire the pipeline before any logic exists.
function splitWords(text) { /* returns array of strings */ }
function countWords(words) { /* returns object { word: count } */ }
function topWord(counts) { /* returns string */ }

function pipeline(text) {
  return topWord(countWords(splitWords(text)));
}

console.log(typeof pipeline);
console.log(pipeline("any text"));
flowchart LR
  T["Too little plan"] --> C["Chaos"]
  P["Too much plan"] --> N["Never start"]
  G["Just enough"] --> W["Working code<br/>informs next step"]
  style G fill:#1e7f3d,color:#fff
  style C fill:#b91c1c,color:#fff
  style N fill:#b91c1c,color:#fff
Too little leads to chaos; too much leads to paralysis. Just enough lets working code guide the next step.
Exercise

What does this print? Track how find searches the milestones array.

const roadmap = {
  milestones: [
    { name: "A", complete: true },
    { name: "B", complete: false },
    { name: "C", complete: false }
  ]
};
function currentTask(r) {
  return r.milestones.find(m => !m.complete)?.name;
}
console.log(currentTask(roadmap));
Exercise

Which quality makes a milestone testable?

Exercise

Write allComplete(milestones) that returns true only if every object in the array has complete: true. An empty array should also return true.

function allComplete(milestones) {
  // return true if every milestone is complete
}
Exercise

This function mixes calculation and display, so nothing it computes can be tested. Fix it to return the status string instead of logging it.

function statusLine(items) {
  const done = items.filter(i => i.done).length;
  console.log(`${done} of ${items.length} done`);
}
Exercise

Write designMilestones(names) that takes an array of strings and returns an array of milestone objects. Each object must have name set to the string and complete set to false.

function designMilestones(names) {
  // return [{ name, complete: false }, ...]
}

Recap

  • A specification breaks a project into milestones that are testable, independent, and visible.
  • Pick an array when order and looping matter; pick an object when you look up by name.
  • Write function signatures before bodies. They act as contracts and become your test outline.
  • Plan enough to start, then let working code refine the plan.

In the next lesson you will build your first capstone tool: a word-frequency tally that applies these planning steps to real code.

Checkpoint quiz

When should you choose an object over an array?

What is the main benefit of writing function signatures before their bodies?

Go deeper — technical resources