skip to content

Why is implementing Runnable generally preferred over extending Thread?

level: middleimportance: must knowfreq 80%

answer

  1. Composition over inheritance
  2. Keeps the single inheritance slot free
  3. Decouples task from execution → fits thread pools
  4. Runnable is the currency of ExecutorService
  5. Functional interface → can be a lambda; easy to test

basics

~20 s

Runnable keeps your task separate from the thread, so you can still extend another class, reuse the task, and hand it to a thread pool. Extending Thread locks your class into the thread machinery and uses up Java's single inheritance.

solid answer

~50 s

Implementing Runnable is preferred mainly because it favors composition over inheritance. A Runnable is a plain task object decoupled from the execution mechanism: the same task can run on a raw Thread, be submitted to an ExecutorService thread pool, scheduled, or reused — and it stays easy to unit-test as an ordinary object. Extending Thread, by contrast, makes your class *be* a thread, which spends Java's single inheritance slot (you can't extend any other class) and tangles 'what to do' with 'how it's run.' Runnable is also a functional interface, so it can be a concise lambda. In practice you rarely create raw Threads at all — you write Runnable/Callable tasks and submit them to an executor, which manages pooling, lifecycle, and back-pressure. So the deeper reason is that Runnable separates policy (when/where to run) from mechanism (the task itself).

code

java · 9 lines
java
// Same task, different runners — the point of Runnable.
Runnable task = () -> System.out.println("work");

new Thread(task).start();                       // run on a raw thread

ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(task);                              // run on a pooled thread
pool.submit(task);                              // reuse the SAME pool threads
pool.shutdown();

go deeper

for a junior

Give the headline reasons: keeps your inheritance slot, separates the task from the thread, works with thread pools.

for a middle

Articulate composition-over-inheritance, list decoupling/reuse/pooling/testability/lambda, and know Callable as the result-returning sibling.

for a senior

Frame it around executor frameworks: tasks should be Runnables submitted to pools; raw Thread creation is a smell. Distinguish performance vs design rationale.

for a principal

Position Runnable as the seam that lets execution policy (pool size, scheduling, virtual vs platform threads, structured concurrency) evolve without touching task code — a stable abstraction boundary.

## The principle: composition over inheritance A recurring design guideline is to prefer **composition** (an object *has* a part it delegates to) over **inheritance** (an object *is* a kind of something). Extending Thread says 'my class **is** a thread.' Implementing Runnable says 'my class **is** a task, and some thread will run it.' The second keeps the task and the runner as separate, swappable pieces. ## Concrete reasons Runnable wins 1. **Single inheritance is precious.** Java lets a class extend only **one** class. If you `extends Thread`, that slot is gone — you can't also extend, say, a domain base class. Implementing Runnable (an interface) leaves you free to extend whatever you need and implement other interfaces too. 2. **Decoupling task from execution.** A Runnable doesn't know or care *how* it runs. The same task can be: run on a `new Thread(task)`, submitted to an `ExecutorService` (a managed pool of reusable threads), scheduled later, or run inline in a test. With `extends Thread`, the task is welded to one specific thread object. 3. **Reusability & pooling.** Creating OS threads is expensive. Real systems use **thread pools** that reuse a few threads for many tasks. Pools accept Runnables (`pool.submit(task)`), not Thread subclasses. So Runnable is the currency of the executor framework; Thread subclasses don't fit. 4. **Testability.** A Runnable is an ordinary object — you can call its `run()` in a unit test and assert on its effects without spawning threads. 5. **Conciseness.** Runnable is a **functional interface** (one abstract method, `run()`), so it can be written as a lambda: `() -> doWork()`. No subclass boilerplate. 6. **Clear single responsibility.** Mixing your business logic into a Thread subclass conflates two concerns: the work, and thread lifecycle/scheduling. Runnable isolates the work. ## When extends Thread is OK (rare) If you genuinely need to override Thread behavior itself (e.g. customize how the thread behaves at a low level) subclassing can be justified, but this is uncommon. Even then, prefer a `ThreadFactory` to configure threads. ## The modern reality Idiomatic Java rarely creates `Thread` objects by hand at all. You write `Runnable` (or `Callable`, which returns a value) tasks and submit them to an `ExecutorService`. The executor owns thread creation, reuse, queuing, and shutdown. This is the ultimate expression of 'separate the task from the thread' — and it only works because your task is a Runnable, not a Thread. ## Summary Prefer Runnable because it composes instead of inherits: it keeps your inheritance slot, decouples the task from how it runs, enables pooling/reuse/testing, and reads as a lambda. Extending Thread couples task to mechanism and is justified only in rare low-level cases.

  • If you need the task to return a result, what do you use instead of Runnable?
    Callable<T>, whose call() method returns a value and may throw a checked exception. You submit it to an ExecutorService and get back a Future<T> to retrieve the result.
  • Does using Runnable instead of Thread give better performance?
    Not directly — a single Runnable on a Thread is the same cost as a Thread subclass. The performance win comes indirectly: Runnable fits thread pools, which avoid the cost of creating a fresh OS thread per task.

A Runnable is a job ticket; extending Thread is hiring an employee who can only ever do that one job and nothing else. Tickets can be handed to any worker or a whole staffing agency (a thread pool); the welded-on employee can't be reassigned.

saying these in an interview costs you the question

  • Saying Runnable is preferred because it's faster — the real reasons are design: composition, decoupling, poolability.
  • Claiming you can extend multiple classes with Runnable — you still have single inheritance; Runnable just doesn't consume it (it's an interface).
  • Asserting you should always extend Thread for 'real' threads — both produce real threads; Runnable is the idiomatic default.
  • Forgetting that thread pools consume Runnables/Callables, not Thread subclasses.

context