Skip to main content

Sign in to CleanKata

Track your progress, earn XP, and unlock every lesson.

By signing in you agree to our Terms of Use and Privacy Policy.

Clean Architecture70 XP7 min

Policy and Level

Level is the distance from inputs and outputs — the farther from I/O, the higher the level. Source code dependencies should always point toward the highest-level, most stable policies.

Why this matters

Every policy in a system has a level — defined by its distance from inputs and outputs. Reading from a file is low-level: it's directly adjacent to an input device. Writing to a display is low-level: it's adjacent to an output device. An encryption algorithm, however, is high-level: it transforms data but doesn't care where data came from or where it will go. It is maximally distant from any particular I/O device.

This concept governs dependency direction. Low-level policies change often — file formats, API shapes, UI structures. High-level policies change rarely — business logic, algorithms, fundamental rules. If a low-level policy depends on a high-level one (correctly), a change to the file format never touches the encryption logic. If the dependency is reversed — if the high-level algorithm knows about the file — a trivial change to input format requires touching the most valuable code in the system.

✗The problem

An encrypt() function that simultaneously opens files, iterates characters, and applies the cipher — all levels mixed in one function, impossible to reuse the cipher logic alone.

Bad

import sys

def encrypt(input_path: str, output_path: str, shift: int) -> None:
    # Low-level: file I/O (close to input/output devices)
    with open(input_path, "r") as fin, open(output_path, "w") as fout:
        for line in fin:
            result = ""
            for char in line:
                # High-level: cipher rule (farthest from I/O)
                if char.isalpha():
                    base = ord("A") if char.isupper() else ord("a")
                    result += chr((ord(char) - base + shift) % 26 + base)
                else:
                    result += char
            fout.write(result)

# The cipher algorithm is buried inside file-open and file-write.
# To reuse the cipher for HTTP, stdin, or sockets: copy-paste only.
# To test the cipher alone: must provide real files.
# All levels are at the same level — there is no level.
import { readFileSync, writeFileSync } from "fs";

export function encryptFile(inputPath: string, outputPath: string, shift: number): void {
  const content = readFileSync(inputPath, "utf-8");  // low-level

  const result = content.split("").map(char => {     // mixed levels
    if (/[a-z]/i.test(char)) {
      const base   = char >= "a" ? 97 : 65;
      const offset = (char.charCodeAt(0) - base + shift) % 26;
      return String.fromCharCode(base + offset);     // high-level cipher rule
    }
    return char;
  }).join("");

  writeFileSync(outputPath, result, "utf-8");        // low-level
}
// The cipher is trapped inside file I/O. Not reusable. Not independently testable.

✓The solution

Pure cipher function at the highest level — knows nothing about I/O. File and stdin wrappers at lower levels depend on it, not the reverse.

Good

# High-level policy (farthest from I/O) — pure and reusable
def caesar_cipher(text: str, shift: int) -> str:
    result = []
    for char in text:
        if char.isalpha():
            base = ord("A") if char.isupper() else ord("a")
            result.append(chr((ord(char) - base + shift) % 26 + base))
        else:
            result.append(char)
    return "".join(result)

# Lower-level: file-based wiring (depends on caesar_cipher, not vice versa)
def encrypt_file(input_path: str, output_path: str, shift: int) -> None:
    with open(input_path, "r") as fin, open(output_path, "w") as fout:
        for line in fin:
            fout.write(caesar_cipher(line, shift))

# Lower-level: stdin/stdout wiring (depends on the same high-level rule)
def encrypt_stdin(shift: int) -> None:
    import sys
    for line in sys.stdin:
        sys.stdout.write(caesar_cipher(line, shift))

# Dependency direction: encrypt_file → caesar_cipher (low → high, correct)
# Test: assert caesar_cipher("Hello", 1) == "Ifmmp"  — no file system needed.
// High-level policy — depends on nothing, changes only for business reasons
export function caesarCipher(text: string, shift: number): string {
  return text.split("").map(char => {
    if (/[a-z]/i.test(char)) {
      const base   = char >= "a" ? 97 : 65;
      const offset = (char.charCodeAt(0) - base + shift) % 26;
      return String.fromCharCode(base + offset);
    }
    return char;
  }).join("");
}

// Lower-level: file-based wiring
import { readFileSync, writeFileSync } from "fs";
export function encryptFile(inputPath: string, outputPath: string, shift: number): void {
  writeFileSync(outputPath, caesarCipher(readFileSync(inputPath, "utf-8"), shift), "utf-8");
}

// Lower-level: HTTP wiring (same high-level rule, different delivery mechanism)
import type { Request, Response } from "express";
export function encryptEndpoint(req: Request, res: Response): void {
  const { text, shift } = req.body as { text: string; shift: number };
  res.json({ encrypted: caesarCipher(text, shift) });
}

// caesarCipher depends on nothing.
// encryptFile and encryptEndpoint depend on caesarCipher.
// Dependencies flow from volatile (I/O) → stable (cipher rule).
// Test: expect(caesarCipher("Hello", 1)).toBe("Ifmmp");

💡Key takeaway

High-level policies change rarely and for business reasons. Low-level details change often and for technical reasons. Dependencies should flow from volatile to stable — from the I/O boundary inward toward the algorithm. Never the reverse.

🔧 Some exercises may still have errors. If something seems wrong, use the Feedback button (bottom-right of the page) to report it — it helps us fix it fast.

Hint: High-level policies change rarely and for business reasons. Low-level details change often and for technical reasons. Dependencies should flow from volatile to stable.

✗ Your version

Policy and Level — CleanKata — CleanKata