multithreading
thread return values
programming
concurrency
parallel computing

Return value from thread

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Plain threads usually do not "return" values the way normal functions do. The usual pattern is to write the result somewhere the caller can read later, or to use a higher-level abstraction such as a future that wraps the thread result for you.

Why join() Is Not a Return Value

Many beginners assume thread.join() gives back the worker result. In most threading APIs, join() only waits for completion. It does not automatically carry the function's output to the caller.

That means you need one of these designs:

  • shared mutable state
  • a queue
  • a promise or future
  • a thread pool API that already exposes .result()

The best choice depends on whether you want one result, many results, streaming results, or exception handling.

The Simplest Pattern: Shared Container

If you are writing a small example, a worker can store its result into a shared object.

python
1import threading
2
3result = {"value": None}
4
5def worker():
6    result["value"] = sum(range(1, 6))
7
8thread = threading.Thread(target=worker)
9thread.start()
10thread.join()
11
12print(result["value"])  # 15

This works, but it scales badly. Once you have multiple workers, failures, or partial results, ad hoc shared containers become messy.

A Better Basic Pattern: Queue

A queue is a safer handoff mechanism because the worker produces a value and the caller consumes it explicitly.

python
1import queue
2import threading
3
4output = queue.Queue()
5
6def worker(n: int):
7    output.put(n * n)
8
9thread = threading.Thread(target=worker, args=(12,))
10thread.start()
11thread.join()
12
13print(output.get())  # 144

This pattern is nice because:

  • the result handoff is explicit
  • multiple workers can all push into the same queue
  • you can also push error markers or structured messages

The Most Convenient Pattern: ThreadPoolExecutor

If your language or library offers futures, prefer them. In Python, concurrent.futures.ThreadPoolExecutor is usually the cleanest way to get a result from threaded work.

python
1from concurrent.futures import ThreadPoolExecutor
2
3def compute_total(n: int) -> int:
4    return sum(range(n + 1))
5
6with ThreadPoolExecutor(max_workers=2) as executor:
7    future = executor.submit(compute_total, 10)
8    value = future.result()
9
10print(value)  # 55

This solves several problems at once:

  • you get the return value directly
  • exceptions are preserved and re-raised on result()
  • you do not need to build your own synchronization protocol

For many modern programs, this is the answer to "How do I return a value from a thread?"

Handling Exceptions Matters Too

A result channel should also handle failure. Futures do this well.

python
1from concurrent.futures import ThreadPoolExecutor
2
3def divide(a: int, b: int) -> float:
4    return a / b
5
6with ThreadPoolExecutor(max_workers=1) as executor:
7    future = executor.submit(divide, 10, 0)
8    try:
9        print(future.result())
10    except ZeroDivisionError as exc:
11        print("worker failed:", exc)

If you used a plain shared variable instead, you would need your own error-signaling convention.

Multiple Results

If several threads produce results, futures still work well.

python
1from concurrent.futures import ThreadPoolExecutor, as_completed
2
3def square(n: int) -> int:
4    return n * n
5
6with ThreadPoolExecutor(max_workers=4) as executor:
7    futures = [executor.submit(square, n) for n in range(5)]
8
9    for future in as_completed(futures):
10        print(future.result())

This is often better than manually joining threads and then reading from shared lists, because the completion order and error handling are already structured.

What About Other Languages

The idea is the same across languages:

  • Java uses Callable and Future
  • C# uses Task<T>
  • C++ uses std::future
  • JavaScript workers usually post messages back rather than returning directly

So the general rule is stable even when syntax changes: raw thread objects are low-level execution tools, while result-bearing abstractions live one layer above them.

When a Shared Variable Is Still Fine

There are cases where a shared variable is enough:

  • a tiny script
  • one worker
  • simple immutable result
  • obvious lifecycle

But once the code grows, queues and futures are easier to reason about and easier to extend.

Common Pitfalls

  • Expecting join() to give the worker's return value.
  • Using shared mutable state without synchronization or a clear ownership rule.
  • Ignoring exceptions raised in the worker and only checking for "missing result."
  • Overusing raw threads when a future-based API already exists.
  • Forgetting that multiple workers writing into the same container need coordination.

Summary

  • Threads usually do not return values directly through join().
  • Small programs can use a shared container, but queues are cleaner.
  • Future-based APIs such as ThreadPoolExecutor are usually the most practical solution.
  • Futures also handle exceptions and multiple tasks better than ad hoc shared variables.
  • If you want a result-bearing abstraction, prefer a higher-level concurrency API over raw threads.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.