multithreading
thread-return-value
concurrency
parallel-programming
thread-management

Returning a value from thread?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Threads do not usually "return" values the way ordinary functions do because the caller does not get the result immediately on the same call stack. In practice, you return a thread result by placing it somewhere the caller can retrieve later, most often through a future, a queue, or a shared result object synchronized with thread completion.

The Best High-Level Option: Futures

When the language runtime provides futures, they are usually the cleanest answer because they bundle three things together:

  • the background task
  • the eventual result
  • any exception raised by the task

In Python, ThreadPoolExecutor gives you this pattern directly.

python
1from concurrent.futures import ThreadPoolExecutor
2
3
4def compute_total():
5    return sum(range(1, 6))
6
7
8with ThreadPoolExecutor(max_workers=1) as executor:
9    future = executor.submit(compute_total)
10    result = future.result()
11
12print(result)

future.result() waits until the thread finishes, then returns the computed value. If the task failed, the exception is re-raised there, which is much safer than silently losing errors in a background thread.

Using a Queue With Plain Threads

If you are working with low-level thread objects, a queue is a practical way to pass values back to the main thread.

python
1import queue
2import threading
3
4
5def worker(result_queue):
6    value = sum(range(10))
7    result_queue.put(value)
8
9
10result_queue = queue.Queue()
11thread = threading.Thread(target=worker, args=(result_queue,))
12thread.start()
13thread.join()
14
15result = result_queue.get()
16print(result)

This pattern is explicit and thread-safe. It also scales well when you have multiple worker threads producing results for a consumer.

Shared State Works, but It Needs Discipline

Another possibility is to write the result into a shared object and wait for the thread to finish.

python
1import threading
2
3
4class ResultHolder:
5    def __init__(self):
6        self.value = None
7
8
9def worker(holder):
10    holder.value = "finished"
11
12
13holder = ResultHolder()
14thread = threading.Thread(target=worker, args=(holder,))
15thread.start()
16thread.join()
17
18print(holder.value)

This works for simple cases, but it becomes fragile if several threads update the same object or if the main thread tries to read the result before completion. Futures and queues make those coordination rules clearer.

Why join() Alone Is Not Enough

join() only waits for a thread to finish. It does not retrieve a value by itself.

python
thread.join()

That line tells you the thread is done, but you still need a place where the result was stored. That is why thread result handling is really about communication patterns, not about a hidden return statement.

Exceptions Matter Too

One of the strongest reasons to prefer futures is exception propagation. With a raw thread, an error in the worker can be easy to miss unless you log or capture it deliberately. With a future, the error is exposed at the point where you ask for the result.

That matters in production systems because "no return value arrived" and "the task crashed" are very different situations.

Common Pitfalls

The most common mistake is expecting the thread target function's return statement to automatically hand a value back to the caller. In most threading APIs, that return value is simply ignored unless the framework wraps it in a higher-level abstraction such as a future.

Another pitfall is reading shared state before the thread has finished. If you use a shared object for the result, make sure the reading side waits appropriately, usually with join() or another synchronization primitive.

Developers also underestimate exception handling. A background thread that fails silently can look like a missing return value when the real issue is an uncaught error.

Summary

  • Threads do not usually return values directly like normal function calls.
  • Futures are the cleanest high-level way to run work and retrieve its result later.
  • Queues are a solid choice for plain threads and producer-consumer patterns.
  • Shared result objects can work, but they require careful synchronization.
  • 'join() waits for completion, but it does not by itself provide a result channel.'

Course illustration
Course illustration

All Rights Reserved.