Algorithmic Thinking & Problem Solving~20 min

Inputs, Outputs, Constraints, and Edge Cases

Specify exactly what a solution takes in, what it produces, and which unusual inputs it must still handle correctly — before writing any code.

By the end of this lesson, you can

  • Specify a problem's inputs, outputs, and constraints before attempting a solution
  • Identify edge cases a solution must handle correctly, not just the typical case
  • Explain why a solution that only handles typical inputs can still contain a hidden logic error

Why it matters

Decomposing a problem tells you what pieces to build. Before building any of them, it pays to specify exactly what each piece takes in, what it’s supposed to produce, and — critically — which unusual inputs it still has to get right. Skipping this step is how a program ends up working perfectly on every input someone happened to try, and silently wrong on the first one nobody thought of.

Mental model

For any problem, write down four things before implementing it:

  • Inputs — what comes in, and its type or shape
  • Outputs — what comes out, and its type or shape
  • Constraints — guarantees or restrictions on the input (always positive? always sorted? never empty?)
  • Edge cases — unusual or boundary inputs the solution must still get right: an empty collection, a single item, a duplicate, the smallest or largest allowed value

The edge cases are the part that’s easy to skip, because a typical input “just works” without ever forcing you to think about them.

Small example

Take a function that returns a list’s first and last item. (items[-1] is Python’s shorthand for “the last item” — a negative index counts backward from the end, so -1 is always the last position no matter how long the list is.)

Python
def first_and_last(items):
    return items[0], items[-1]

print(first_and_last([1, 2, 3, 4]))
Output
(1, 4)

That works for the typical case. Now specify it properly:

  • Input: a list of any values
  • Output: a pair — the first item and the last item
  • Constraint: none stated (any list is accepted)
  • Edge cases: an empty list, and a list with exactly one item

Neither edge case was tested by the example above.

Trace it

Checking first_and_last against the typical case and its edge cases
InputExpected behaviorWhat actually happens
[1, 2, 3, 4]returns (1, 4)(1, 4) — correct
[5]returns (5, 5) — first and last are the same item(5, 5) — correct, if intended
[]unclear — the problem never saidIndexError: list index out of range

The single-item case turns out fine once you think it through — but the empty-list case exposes something the original specification never answered: what should happen for an empty list? Without deciding that up front, the function’s behavior there is really just an accident of how items[0] happens to fail.

Check your understanding

A function averages a list of numbers. Which input is the edge case most likely to be overlooked?
Or reveal the answer without checking

Answer:An empty list
An empty list forces a division by zero (0 items to divide the sum by) — a boundary condition that never comes up while testing with ordinary, non-empty lists.

Why specify edge cases before writing any code, rather than testing for them afterward?
Or reveal the answer without checking

Answer:Deciding a solution's intended behavior for an edge case is a design decision — finding it accidentally, as a crash, means the decision was never actually made
Specifying behavior in advance means you consciously decided what an empty list, a single item, or a boundary value should do — rather than whatever happens to occur once someone tries it and it breaks.

Practice: warm-up

For this problem, write down its inputs, outputs, constraints, and at least two edge cases: “Given a list of exam scores, return the highest score.”

Stuck? Reveal one hint at a time.

  1. Hint 1

    What should happen if the list has only one score? That's a legitimate edge case even though it feels trivial.

  2. Hint 2

    What should happen if the list is empty — is there a "highest score" of nothing?

Reveal one reasonable specification

Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.

Input: a list of numbers (exam scores)
Output: a single number (the highest score)
Constraints: none stated — scores could include duplicates or be in any order
Edge cases:
  - a list with exactly one score (the answer is that score)
  - an empty list (undefined by the problem — needs a decision: error, or None?)
  - all scores tied (the answer is just that shared value)

Practice: apply it

This function is meant to report which word in a list has the most vowels:

def most_vowels(words):
    best_word = None
    best_count = 0
    for word in words:
        vowel_count = 0
        for letter in word:
            if letter in "aeiou":
                vowel_count = vowel_count + 1
        if vowel_count > best_count:
            best_word = word
            best_count = vowel_count
    return best_word
What does most_vowels([]) return, and is that a reasonable answer?
Or reveal the answer without checking

Answer:It returns None, since best_word is never reassigned — arguably reasonable, but only if that was actually decided on purpose
With no words, the outer loop body never runs, so best_word stays None — its starting value. That's not a crash, but whether None is the intended answer for 'no words at all' is exactly the kind of decision this lesson says to make deliberately, not by accident.

Modification challenge: decide what most_vowels([]) should return, then add a check at the top of the function that makes that decision explicit instead of leaving it to happen by accident.

Summary

  • Before implementing, specify a problem’s inputs, outputs, constraints, and edge cases explicitly.
  • An edge case is a boundary or unusual input — empty, single-item, duplicate, extreme value — not the typical case a first attempt is usually tested against.
  • A solution that only handles typical inputs can still contain a hidden logic error, waiting for whichever edge case nobody thought to specify.
  • Deciding a solution’s behavior for an edge case in advance is a design decision; discovering it as a crash later means that decision was never actually made.

Key terms