← Back to principles

Design Principle

KISS — Keep It Simple, Ship It

KISS emphasizes clarity over cleverness. Learn to reduce hidden state, optimize for common cases, and choose names that tell the story effectively and clearly.

KISS — Keep It Simple, Ship It

Why This Matters

Think of KISS like choosing the right tool for the job. You could use a Swiss Army knife with 50 tools, but if you just need to cut something, a simple knife is better. Similarly, you could build a complex abstraction with 10 layers, but if a simple function solves the problem, use the simple function.

This matters because complexity accumulates. A small abstraction today seems harmless, but after months of similar abstractions, your codebase becomes hard to understand. New engineers take weeks to understand the code. Bug fixes take days instead of hours. KISS helps you avoid this by keeping things simple.

In interviews, when someone asks "How would you refactor this code?", they're testing whether you understand KISS. Can you simplify complex code? Do you know when to add complexity and when to avoid it? Most engineers can't. They add complexity "just in case" and wonder why the codebase becomes unmaintainable.

What Engineers Usually Get Wrong

Engineers often think "simple means incomplete." But simple doesn't mean incomplete—it means clear and easy to understand. A simple solution that solves the problem is better than a complex solution that solves problems you don't have yet. You can always add complexity later when you actually need it.

Engineers also apply KISS too late. They build complex systems, then try to simplify them. It's easier to start simple and add complexity when needed than to start complex and simplify later. Start with the simplest solution that works, then evolve based on evidence.

How This Breaks Systems in the Real World

A team was building a feature that needed to process 100 items. A developer optimized it to handle 1 million items using complex algorithms and data structures. The optimization made the code hard to understand and debug. But the feature never processed more than 100 items. The optimization was unnecessary and made the code worse.

The fix? Simplify to handle 100 items. Optimize only if you actually need to handle more. This is KISS in action—solve the problem you have, not the problem you might have.

Another story: A service had a simple function that validated user input. Over time, developers added "just in case" features—support for multiple formats, custom validators, complex error messages. The function grew from 10 lines to 200 lines. It became hard to understand and test. The fix? Simplify. Remove features that aren't actually used. Keep only what's needed.


1. Reduce hidden state

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

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

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.

1// Before
2function handle(data: Payload) {
3 return exec(data);
4}
5
6// After
7function submitExpenseReport(report: ExpenseReport) {
8 return 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:

1// Violation: Complex builder pattern
2const user = User.create()
3 .withProfile(profileFactory("author"))
4 .withNotifications(enabledNotifications())
5 .build();
6
7// KISS approach: Simple data structure
8const user = {
9 profile: authorProfile(),
10 notifications: enabledNotifications(),
11};
12registerUser(user);

This requires understanding the builder pattern, factory methods, and context propagation. The KISS approach exposes the data structure directly, making it immediately clear what's being created.

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."


Failure Stories You'll Recognize

The Premature Optimization: A team was building a feature that needed to process 100 items. A developer optimized it to handle 1 million items using complex algorithms and data structures. The optimization made the code hard to understand and debug. But the feature never processed more than 100 items. The optimization was unnecessary and made the code worse. The fix? Simplify to handle 100 items. Optimize only if you actually need to handle more. This is KISS in action.

The Feature Creep: A service had a simple function that validated user input. Over time, developers added "just in case" features—support for multiple formats, custom validators, complex error messages. The function grew from 10 lines to 200 lines. It became hard to understand and test. The fix? Simplify. Remove features that aren't actually used. Keep only what's needed.

The Abstraction That Hid Everything: A team created a complex abstraction layer that hid all implementation details. This seemed elegant. But when bugs appeared, debugging was impossible—you had to understand 5 layers of abstraction to find the problem. The fix? Simplify. Make dependencies explicit. Don't hide what needs to be visible.

What Interviewers Are Really Testing

They want to hear you talk about simplicity as a deliberate choice, not laziness. Junior engineers say "keep it simple." Senior engineers say "KISS means solving the problem you have, not the problem you might have. Start simple, evolve based on evidence. Measure complexity in 'how long to understand,' not lines of code."

When they ask "How would you refactor this code?", they're testing:

  • Can you simplify complex code?

  • Do you know when to add complexity and when to avoid it?

  • Can you explain your design decisions clearly?

  • 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

How InterviewCrafted Will Teach This

We'll teach this through real code problems, not abstract principles. Instead of memorizing "KISS means Keep It Simple," you'll learn through scenarios like "this code is complex—should I simplify it?"

You'll see how simplicity helps you write maintainable code. When an interviewer asks "how would you refactor this code?", you'll think about clarity, dependencies, and complexity—not just "make it simple."

  • DRY, KISS & YAGNI - KISS is one of three fundamental principles. Learn how DRY, KISS, and YAGNI work together to create maintainable code.
  • SOLID Principles - SOLID principles complement KISS by providing object-oriented design guidelines that emphasize simplicity and clarity.
  • YAGNI — You Aren't Gonna Need It - YAGNI and KISS work together to prevent over-engineering and keep code simple until evidence demands complexity.

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.