← Back to principles

Design Principle

KISS — Keep It Simple, Ship It

Design for clarity first; optimise for comprehension, then extensibility.

Simplicity isn't laziness. It’s a deliberate choice to align the design with the problem at hand so that future maintainers can move quickly.


1. Reduce hidden state

Invisible state breeds surprise. Prefer explicit data flow over magical singletons.

No hidden fallbacksTS
1// ❌ Implicit dependencies via module scope.
2let client: DatabaseClient;
3
4export function init(connectionString: string) {
5client = new DatabaseClient(connectionString);
6}
7
8export async function findUser(id: string) {
9return client.query("SELECT * FROM users WHERE id = $1", [id]);
10}
11
12// ✅ Pass the dependency explicitly.
13type UserRepository = {
14find(id: string): Promise<User | null>;
15};
16
17export function createUserRepository(client: DatabaseClient): UserRepository {
18return {
19 async find(id) {
20 return client.query(/* ... */);
21 },
22};
23}

Why: Simple designs expose every dependency at the call site, making testing and change review effortless.


2. Optimise for the common case

If 95% of calls are straightforward, don’t let the rare edge dominate your API.

1// ❌ Config explosion for an API with one consumer.
2createReport({ format, timezone, includeArchived, cachingStrategy, ... });
3
4// ✅ Accept parameters you need today; expose optional behaviours later.
5createReport({ format: "pdf" });

Why: Every extra parameter is a future obligation. Simplicity limits the surface area you must support forever.


3. Name for intent

KISS extends to naming: spend the time to choose verbs that tell the story.

Refine names until the story reads cleanlyTS
1// Before
2function handle(data: Payload) {
3return exec(data);
4}
5
6// After
7function submitExpenseReport(report: ExpenseReport) {
8return workflowProcessor.process(report);
9}

Why: The simplest API is the one the reader understands without opening its implementation.


Quick heuristics

  1. Can you explain it on a whiteboard in two minutes? If not, simplify.
  2. Would a junior engineer understand the flow? Clarity beats cleverness.
  3. How fast can you delete it? Simple designs have obvious teardown steps.

Reflection prompt

List the three most complex modules you own. For each, describe its purpose in a single sentence. If the sentence contains multiple clauses, you have a candidate for a KISS refactor.

Takeaway: Ship a simple design today, measure how well it adapts tomorrow, and iterate with evidence.


Interview Questions

Beginner

Q: What does KISS stand for and why is it important in software development?

A: KISS stands for "Keep It Simple, Ship It" (or "Keep It Simple, Stupid"). It emphasizes designing for clarity and comprehension over clever abstractions. Simple code is easier to understand, test, debug, and modify. The goal is to optimize for readability—measure complexity in "how long to understand" rather than lines of code or clever patterns.


Intermediate

Q: Give an example of code that violates KISS. How would you simplify it?

A: Violation example - chained factories with context-aware constructors:

const user = User.create()
  .withProfile(profileFactory("author"))
  .withNotifications(enabledNotifications())
  .build();

This requires understanding the builder pattern, factory methods, and context propagation. A KISS approach:

const user = {
  profile: authorProfile(),
  notifications: enabledNotifications(),
};
registerUser(user);

The simple version exposes the data structure directly, making it immediately clear what's being created. When onboarding teammates, note where explanations feel apologetic—that's your KISS debt.


Senior

Q: You're building a distributed task queue system. How do you apply KISS while ensuring it can scale to handle millions of tasks? What would you build first vs. defer?

A:

Start simple (KISS):

  • Single queue table in database with status (pending, processing, completed, failed)
  • Polling-based workers that SELECT tasks with status='pending' LIMIT 100
  • Simple retry logic: update status to 'failed' after N attempts
  • No complex priority queues, no distributed locking yet

Why this works:

  • Easy to understand and debug (just query the database)
  • Works for first 10K-100K tasks
  • Can explain the entire flow in 2 minutes

Defer until evidence demands:

  • Redis-based queues (wait until database becomes bottleneck)
  • Priority queues (wait until you have actual priority requirements)
  • Distributed locking with ZooKeeper/etcd (wait until you have concurrent worker conflicts)
  • Dead letter queues (wait until you have patterns in failures)

Evolution path:

  1. When database becomes slow → move hot queue to Redis, keep metadata in DB
  2. When you need priorities → add priority field, sort by priority
  3. When you have worker conflicts → add simple advisory locks
  4. When failures cluster → add dead letter queue

Key insight: The simple design has obvious teardown steps. You can migrate incrementally without rewriting everything. KISS doesn't mean "never scale"—it means "scale when the system insists, not when imagination suggests."


Key Takeaways

  • Reduce hidden state - prefer explicit data flow over magical singletons or global variables
  • Optimize for the common case - if 95% of calls are straightforward, don't let rare edges dominate your API
  • Name for intent - choose verbs that tell the story; the simplest API is one understood without reading implementation
  • Heuristics: Can you explain it on a whiteboard in 2 minutes? Would a junior engineer understand? How fast can you delete it?
  • Simple designs expose dependencies - every dependency visible at call site makes testing and change review effortless
  • KISS extends to naming - spend time choosing verbs that tell the story clearly
  • Measure complexity in "how long to understand" - not lines of code or pattern sophistication
  • Simple designs have obvious teardown steps - you can evolve incrementally when evidence demands it

Keep exploring

Principles work best in chorus. Pair this lesson with another concept and observe how your architecture conversations change.