{"post":{"slug":"how-to-write-clean-functions-2026","title":"How to write clean functions in 2026","excerpt":"Clean functions are the single highest-leverage thing you can do for code quality. Here is the 2026 playbook with concrete rules and real examples.","category":"code","date":"March 18, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">C</span>lean functions are not about aesthetics — they are about the cost of change. A function that does one clear thing can be read in 30 seconds, tested in isolation, and modified without fear. A function that does five things requires understanding all five, breaking any of them fails silently, and every change is a gamble.\n\n## What changed in 2026\n\n- **AI-assisted code review flags complexity automatically.** Tools like GitHub Copilot and Cursor highlight cyclomatic complexity > 10 inline. But they generate complex functions too, so the discipline matters more, not less.\n- **TypeScript is the default.** Strong types surface bad function signatures before runtime; they make \"what does this function accept and return\" an answered question.\n- **Functional patterns are mainstream.** Immutability, pure functions, and composable pipelines are now taught in onboarding, not just FP circles.\n- **LLMs struggle with tangled functions.** If your function is hard for a model to reason about, it is hard for humans too — use that as a code smell signal.\n\n## The single-responsibility rule\n\nA function should do exactly one thing at one level of abstraction. The clearest test: can you describe it without the word \"and\"?\n\n```typescript\n// Bad — two jobs: validation AND transformation\nfunction processEmail(email: string): User {\n  if (!email.includes(\"@\")) throw new Error(\"invalid\");\n  return { email: email.toLowerCase().trim() };\n}\n\n// Good — separate concerns\nfunction validateEmail(email: string): void {\n  if (!email.includes(\"@\")) throw new Error(\"invalid email\");\n}\n\nfunction normaliseEmail(email: string): string {\n  return email.toLowerCase().trim();\n}\n```\n\n## Naming\n\n| Pattern | Example | Why it works |\n| --- | --- | --- |\n| Verb + noun | `fetchUser`, `sendInvoice` | Describes the action clearly |\n| `is`/`has`/`can` prefix | `isValid`, `hasPermission` | Signals boolean return |\n| `get` vs `fetch` | `getFullName` (sync), `fetchUser` (async) | Signals I/O vs computation |\n| Avoid | `handle`, `process`, `util` | Too vague — anything fits |\n\nNever abbreviate unless the abbreviation is universal (`url`, `id`, `i` in a loop).\n\n## Length and complexity\n\n```typescript\n// Cyclomatic complexity: every branch +1\n// Target < 5; reject > 10\n\n// Bad: 8 branches in one function\nfunction calculateDiscount(user, cart, coupon) {\n  if (!user) return 0;\n  if (cart.total < 10) return 0;\n  if (user.isPremium) {\n    if (coupon) return 0.3;\n    return 0.2;\n  }\n  if (coupon && coupon.valid) return 0.1;\n  if (cart.items > 5) return 0.05;\n  return 0;\n}\n\n// Better: extract named helpers\nfunction getPremiumDiscount(hasCoupon: boolean): number {\n  return hasCoupon ? 0.3 : 0.2;\n}\n\nfunction getStandardDiscount(coupon: Coupon | null, itemCount: number): number {\n  if (coupon?.valid) return 0.1;\n  if (itemCount > 5) return 0.05;\n  return 0;\n}\n```\n\n## Pure functions\n\nA pure function: same inputs → same output, no observable side effects.\n\n```typescript\n// Impure — mutates external state\nlet count = 0;\nfunction increment() { count++; }\n\n// Pure — returns new value\nfunction increment(n: number): number { return n + 1; }\n\n// Impure — reads external DB inside\nasync function getUser(id: string) {\n  return db.find(id); // side effect!\n}\n\n// Better: inject the dependency\nasync function getUser(id: string, db: Database): Promise<User> {\n  return db.find(id);\n}\n```\n\nInjecting dependencies makes functions pure *relative to their contract* and trivially testable.\n\n## How to pick the right abstraction level\n\n```typescript\n// Mixed levels — bad\nasync function createOrder(items: Item[], userId: string) {\n  const user = await db.query(\"SELECT * FROM users WHERE id = $1\", [userId]);\n  // ... low-level SQL mixed with business logic\n  const total = items.reduce((s, i) => s + i.price, 0);\n  await sendEmail(user.email, `Order total: ${total}`);\n}\n\n// Consistent level — good\nasync function createOrder(items: Item[], userId: string) {\n  const user = await fetchUser(userId);         // same level\n  const order = buildOrder(items, user);        // same level\n  await persistOrder(order);                   // same level\n  await notifyUser(user, order);               // same level\n}\n```\n\nEach line in the good version reads like a sentence in the same paragraph.\n\n## Common mistakes\n\n**Boolean flag parameters.** `render(true)` is unreadable at the call site. Split into `renderWithHeader()` and `renderWithoutHeader()`, or use an options object with a named key.\n\n**Output arguments.** Functions that modify a passed-in object are confusing. Return the modified value instead.\n\n**Deeply nested logic.** Every level of indentation is a tax on the reader. Use early returns (guard clauses) to flatten.\n\n```typescript\n// Nested — hard to read\nfunction process(user) {\n  if (user) {\n    if (user.active) {\n      if (user.hasPermission) {\n        doWork();\n      }\n    }\n  }\n}\n\n// Flat — easy to read\nfunction process(user) {\n  if (!user) return;\n  if (!user.active) return;\n  if (!user.hasPermission) return;\n  doWork();\n}\n```\n\n**Dead parameters.** If a parameter is never used inside the function, remove it. It misleads every caller.\n\n## What to skip\n\n- **Docblock comments for obvious functions.** `// Returns the user name` above `getUserName()` adds noise. Write self-documenting code; save comments for *why*, not *what*.\n- **Premature abstraction.** Don't extract a helper until you've written the duplication twice. \"Rule of Three\" still holds.\n- **Classes for stateless logic.** A module of exported functions is simpler than a class with no instance state.\n\n## FAQ\n\n**How long should a function really be?**\n15–25 lines is the practical sweet spot. Past 40 lines, break it up. Past 60 lines, you almost certainly have multiple responsibilities.\n\n**Should every function be pure?**\nNo — I/O, DB writes, and UI updates are inherently effectful. The goal is to push effects to the edges and keep the core logic pure.\n\n**What about performance — does splitting functions add overhead?**\nNegligible in modern runtimes. V8, the JVM, and LLVM inline small functions aggressively. Write for humans first.\n\n**How do I refactor a 200-line function without breaking it?**\nAdd tests first (even characterisation tests that just capture current output), then extract the smallest identifiable sub-task into a named function and re-run tests after each extraction.\n\n## Where to go next\n\n- [How to handle API errors in 2026](/blog/how-to-handle-api-errors-2026)\n- [How to test a React component in 2026](/blog/how-to-test-a-react-component-2026)\n- [How to write a Bash script in 2026](/blog/how-to-write-a-bash-script-2026)","hue":318,"metaTitle":"How to Write Clean Functions in 2026 (With Examples)","metaDescription":"Practical 2026 guide to writing clean functions: single responsibility, naming, length, pure functions, and the patterns that make codebases maintainable.","keywords":["how to write clean functions 2026","clean code functions 2026","function design best practices 2026","single responsibility principle 2026","pure functions javascript 2026","clean code principles 2026","refactoring functions 2026","function naming conventions 2026","code quality 2026","software craftsmanship 2026"],"tldr":["**One function, one job** — if you need \"and\" to describe what it does, split it.","**Keep it short** — aim for 15–25 lines; past 40 lines the cognitive load spikes fast.","**Name it as a verb phrase** — `fetchUser`, `validateEmail`, `transformOrder`; never `util`, `helper`, or `process`.","**No side effects unless obvious** — pure functions are trivially testable and composable.","**Skip:** boolean parameters that change behaviour (`doThing(flag: true)`) — split into two named functions instead."],"readTime":"4 min read","wordCount":988},"related":[{"slug":"how-to-write-clean-code-2026","title":"How to write clean code in 2026: principles that actually matter","excerpt":"Clean code is not about style guides or linters — it is about writing code that your future self and teammates can understand and change safely.","category":"code","date":"April 6, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":316},{"slug":"how-to-write-better-code-2026","title":"How to Write Better Code in 2026: habits that last","excerpt":"Better code is readable, simple, and tested, not clever. Here are the habits that improve your code in 2026, from naming to small functions to honest tests.","category":"code","date":"June 22, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":222},{"slug":"how-to-write-a-function-2026","title":"How to Write a Function in 2026: a beginner guide","excerpt":"Functions are the building blocks of every program. Learn how to write one in 2026: parameters, return values, and the habits that keep functions easy to use.","category":"code","date":"May 12, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":212}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-write-a-sql-query-2026","title":"How to write a SQL query in 2026","excerpt":"SQL is 50 years old and still the fastest path from question to answer in most databases. This guide covers SELECT fundamentals, joins, aggregations, and modern window functions.","category":"code","date":"March 18, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":315},"older":{"slug":"how-to-write-e2e-tests-2026","title":"How to write e2e tests in 2026","excerpt":"End-to-end tests catch what unit tests miss — broken flows, real browser quirks, and integration failures. This guide covers Playwright setup, writing reliable tests, and running them in CI.","category":"code","date":"March 18, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":316}}