Skip to main content
Clean Code 50 XP · 6 min

Small Functions

Functions should do one thing, do it well, and do it only.

Showing
Ad (728×90)

Do One Thing

Functions that do one thing are easy to name, easy to test, and impossible to misuse. "One thing" means one level of abstraction — not one line of code. If you can extract a piece into a well-named function without it feeling contrived, your original function was doing two things.

Does many things

function processUserRegistration(data):
    // validate
    if not data.email or "@" not in data.email:
        return { error: "invalid email" }
    if not data.password or length(data.password) < 8:
        return { error: "password too short" }
    // hash password
    hashed = hash(data.password)
    // save to db
    user = { email: data.email, password: hashed }
    db.insert("users", user)
    // send email
    sendEmail(data.email, "Welcome!", "Thanks for joining.")
    return { ok: true }

One thing each

function registerUser(data):
    validateRegistration(data)
    user = createUser(data.email, data.password)
    db.save(user)
    sendWelcomeEmail(user)

function validateRegistration(data):
    requireValidEmail(data.email)
    requireStrongPassword(data.password)

function createUser(email, password):
    return { email: email, password: hash(password) }

One Level of Abstraction

Mixing high-level logic (sendEmail()) with low-level detail (hashlib) in the same function is disorienting. Readers can't tell what's important and what's implementation noise. The Step-Down Rule: every function should read like a paragraph — each line calls functions one level below it.

// ✗ Mixed levels — high-level intent buried in low-level noise
function checkout(cart, user):
    total = sum of (item.price * item.qty) for each item in cart.items
    tax = total * 0.08
    final = total + tax
    conn = db.getConnection()        // ← suddenly low-level
    cursor = conn.cursor()
    cursor.execute("INSERT INTO orders ...", user.id, final)
    conn.commit()
    http.post("https://mail.svc/send", { ... })  // ← even lower
// ✓ One level — each line is at the same altitude
function checkout(cart, user):
    total = calculateTotal(cart)
    order = saveOrder(user, total)
    notifyUser(user, order)

Arguments and Side Effects

The ideal number of function arguments is zero. One is fine. Two is acceptable. Three or more — consider a parameter object. Flag arguments (is_admin=True) are a code smell: they announce the function does two things. Side effects are lies — a function named check_password that also logs the user in breaks the single-responsibility contract and surprises every caller.

Flag arg + side effect

function getUser(userId, includeDeleted):
    // does TWO things depending on flag
    if includeDeleted:
        return db.queryAll(userId)
    return db.queryActive(userId)

function checkPassword(user, password):
    if user.password == hash(password):
        session.login(user)   // ← surprise side effect!
        return true
    return false

No flags, no surprises

function getActiveUser(userId):
    return db.queryActive(userId)

function getUserIncludingDeleted(userId):
    return db.queryAll(userId)

function isCorrectPassword(user, password):
    return user.password == hash(password)

function login(user):          // side effect is explicit
    session.start(user)

Code Challenge

This function does at least 4 things. Can you name each responsibility and extract it into its own function?

Key takeaway

A function should do one thing, at one level of abstraction, with a name that makes a comment unnecessary.

Done with this lesson?

Mark it complete to earn XP and track your progress.