Ballerina
Reactive Programming
Threads
Concurrency
Software Development

Threads and Reactive Programming in Ballerina

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

Ballerina emphasizes concurrent and network-friendly programming with a model that differs from manual thread management in many languages. Instead of creating and synchronizing raw threads, developers typically compose asynchronous workflows and rely on the runtime scheduler. Understanding this model helps avoid race conditions and unnecessary complexity.

Core Sections

Ballerina Concurrency Model in Practice

Ballerina supports concurrent workers, asynchronous function calls, and message-passing style coordination. The runtime handles scheduling, so you usually express intent rather than manually controlling OS threads.

ballerina
1import ballerina/io;
2
3function main() {
4    future<string> fa = start fetchData("A", 2);
5    future<string> fb = start fetchData("B", 1);
6
7    string a = wait fa;
8    string b = wait fb;
9
10    io:println(a + " | " + b);
11}
12
13function fetchData(string name, int delaySec) returns string {
14    // Simulate I O or remote call
15    runtime:sleep(delaySec);
16    return "done-" + name;
17}

This pattern provides concurrency without explicit locks in many scenarios.

Use Workers for Structured Parallel Steps

Workers are useful when a function has multiple parallel subflows with clear boundaries.

ballerina
1import ballerina/io;
2
3function process() returns int {
4    worker w1 {
5        int a = 10;
6        -> w2 a;
7    }
8
9    worker w2 {
10        int fromW1 = <- w1;
11        return fromW1 * 2;
12    }
13
14    int result = wait w2;
15    return result;
16}
17
18public function main() {
19    io:println(process());
20}

Workers make message flow explicit and reduce shared mutable state.

Reactive Integration with Streams and Services

In reactive-style applications, Ballerina services react to incoming events such as HTTP requests, messaging events, or stream updates. Instead of polling loops, each event triggers focused processing logic.

ballerina
1import ballerina/http;
2
3service /status on new http:Listener(8080) {
4    resource function get ping() returns string {
5        return "ok";
6    }
7}

This event-driven style aligns with reactive principles: responsiveness, resilience, and elasticity.

Error Handling Across Async Boundaries

When using futures and workers, handle errors explicitly so failures are observable and recoverable.

ballerina
1function safeCall() returns string|error {
2    future<string|error> f = start remoteOperation();
3    string|error res = wait f;
4    if res is error {
5        return error("remote operation failed", res);
6    }
7    return res;
8}
9
10function remoteOperation() returns string|error {
11    return "success";
12}

Propagating typed errors keeps async code maintainable.

When to Avoid Manual Thread Thinking

Applying traditional thread-heavy design can create unnecessary synchronization and state complexity. In Ballerina, prefer immutable data passing and isolated services whenever possible. This naturally reduces concurrency bugs.

If you must share mutable state, isolate access and make ownership explicit.

Design Services Around Backpressure and Timeouts

Reactive systems are not only about concurrency. They also need bounded resource usage and predictable failure behavior. In Ballerina services, set clear timeout policies for outbound calls and avoid unbounded in-memory buffering for incoming traffic.

A practical pattern is to separate request acceptance from heavy processing, then acknowledge quickly and process asynchronously with observable status endpoints. This keeps services responsive under load spikes.

ballerina
1import ballerina/http;
2
3service /jobs on new http:Listener(9090) {
4    resource function post submit(@http:Payload string payload) returns http:Accepted {
5        future<string> f = start backgroundProcess(payload);
6        _ = f;
7        return <http:Accepted>{body: "accepted"};
8    }
9}
10
11function backgroundProcess(string p) returns string {
12    return "processed:" + p;
13}

Explicit load-management choices are critical for reliability in reactive architectures.

Operational visibility is also important. Add structured logs and latency metrics around async boundaries so bottlenecks are easy to identify in production.

Common Pitfalls

  • Translating thread-centric designs directly instead of using Ballerina concurrency primitives.
  • Sharing mutable state widely across workers.
  • Waiting on futures too early and accidentally serializing work.
  • Ignoring typed error returns in asynchronous flows.
  • Building polling loops where event-driven services are more appropriate.

Summary

  • Ballerina concurrency is scheduler-driven and message-oriented.
  • Futures and workers provide structured parallelism without manual thread control.
  • Reactive services naturally fit network and event workloads.
  • Handle errors explicitly across async boundaries.
  • Prefer immutable or isolated state patterns to reduce race-related bugs.

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.