Why is implementing Runnable generally preferred over extending Thread?
answer
- Composition over inheritance
- Keeps the single inheritance slot free
- Decouples task from execution → fits thread pools
- Runnable is the currency of ExecutorService
- Functional interface → can be a lambda; easy to test
basics
~20 sRunnable 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 sImplementing 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// 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
Give the headline reasons: keeps your inheritance slot, separates the task from the thread, works with thread pools.
Articulate composition-over-inheritance, list decoupling/reuse/pooling/testability/lambda, and know Callable as the result-returning sibling.
Frame it around executor frameworks: tasks should be Runnables submitted to pools; raw Thread creation is a smell. Distinguish performance vs design rationale.
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.