Introduction & Context

Welcome back! You've mastered sequential workflows with prompt chaining and conditional workflows with intelligent routing. Now it's time to unlock dramatic performance improvements by learning parallel processing — executing multiple independent OpenAI API calls simultaneously instead of waiting for each one to complete.

In this lesson, you'll discover how to transform workflows that take minutes into operations that complete in seconds. You'll learn the difference between synchronous and asynchronous programming, master TypeScript's native Promise-based concurrency, understand the critical differences between Promise.all() and Promise.allSettled(), and build a production-ready system that asks multiple questions to GPT-5 at the same time while handling failures gracefully.

What "Synchronous" Really Means

Before we dive into the technical details, let's clarify terms that often confuse beginners: synchronous and asynchronous.

Outside of programming, "synchronous" means "happening at the same time" — think synchronized swimming or clocks ticking together. In programming, though, "synchronous" means the opposite: operations are coordinated in sequence, not in parallel.

When we say "synchronous API calls," we mean calls that happen one after another, waiting for each to complete before starting the next. "Asynchronous" API calls, on the other hand, can be launched together and run concurrently — they don't wait for each other to finish.

Here's what makes this possible: Node.js (the runtime for TypeScript) uses an event loop and non-blocking I/O. When you make an asynchronous API call to OpenAI, your code doesn't actually freeze and wait for the response. Instead, Node.js registers your request and immediately continues executing other code. When OpenAI's response arrives, the event loop triggers a callback to process the result.

This architecture is what enables true concurrency in a single-threaded environment — while one API call waits for GPT-5's response over the network, Node.js can initiate and manage other API calls without blocking. Think of it like a restaurant server who takes multiple tables' orders without waiting at each table for the kitchen to finish cooking.

This might seem backward at first, but once you understand this distinction, the terms "synchronous" (sequential) and "asynchronous" (concurrent, non-blocking) will make much more sense throughout this lesson.

The Parallelization Workflow Pattern

Let's understand the high-level pattern we'll be implementing. This workflow has two distinct phases that work together to provide both speed and comprehensive results:

Phase 1: Parallel Research Gathering

  • Launch multiple independent OpenAI API calls simultaneously.
  • Each call researches a different aspect of your topic (attractions, transportation, culture).
  • All questions run concurrently, completing in roughly the time of the slowest individual request.
  • Results are collected and preserved, even if some requests fail.

Phase 2: Sequential Result Synthesis

  • Combine all successful parallel research into a single comprehensive dataset.
  • Send the aggregated information to GPT-5 with instructions for synthesis.
  • Generate a unified, actionable final result (like a complete travel guide).
  • This sequential step ensures all information is properly integrated.

This two-phase approach maximizes both efficiency and quality: you get the speed benefits of parallel processing for data gathering while maintaining coherent analysis through sequential aggregation. It's particularly powerful for research tasks, analysis workflows, and any scenario where you need to quickly gather diverse information and synthesize it into actionable insights.

TypeScript's Native Asynchronous Model

TypeScript (and JavaScript) has built-in support for asynchronous operations through Promises and the async/await syntax. Unlike some languages that require separate libraries for concurrent execution, TypeScript's asynchronous capabilities are native to the language runtime.

A Promise represents a value that may not be available yet but will be resolved in the future. When you call an asynchronous function, it immediately returns a Promise, allowing your program to continue executing other code while waiting for the operation to complete.

import OpenAI from "openai";

// Initialize the OpenAI client (it uses Promises natively)
const client = new OpenAI();

async function askGPT(question: string): Promise<string> {
  // The 'await' keyword pauses this function until the Promise resolves
  // Meanwhile, other async operations can continue running
  const response = await client.responses.create({
    model: "gpt-5",
    instructions: "You are a travel expert. Give brief, helpful answers.",
    input: [{ role: "user", content: question }],
    reasoning: { effort: "minimal" },
    store: false
  });
  
  return response.output_text;
}

The async keyword transforms a regular function into one that returns a Promise, while await pauses execution of that function until the Promise resolves. Importantly, when a function is paused at an await, the JavaScript runtime can execute other code — this is what enables concurrent operations.

Running Async Code in TypeScript

Unlike some languages that require special setup to run asynchronous code, TypeScript's async functions can be called directly. You simply invoke the async function and handle its Promise:

async function main() {
  // Call your async function and await the result
  const result = await askGPT("What are the top 3 must-see attractions in Paris?");
  console.log(result);
}

// Run the async main function with error handling
main().catch((err) => {
  console.error(err);
  process.exit(1);
});

The real power of async programming comes from running multiple operations concurrently. TypeScript provides two main approaches for this: Promise.all() and Promise.allSettled(). Understanding the difference between them is crucial for building robust workflows.

Promise.all: Fast but Fragile

Promise.all() starts multiple Promises simultaneously and waits for all of them to complete, returning results in the original order:

async function basicParallelExample() {
  // Create an array of Promises (these start executing immediately)
  const tasks = [
    askGPT("What are the top attractions in Paris?"),
    askGPT("How do I get around Paris?"),
    askGPT("What are French cultural norms?")
  ];
  
  // Wait for all Promises to complete
  // Results are returned in the same order as the original tasks
  const results = await Promise.all(tasks);
  
  console.log(results);  // Array of three strings
}

This looks elegant, but Promise.all() has a critical limitation: fail-fast behavior. If any single Promise rejects (for example, if one API call fails due to network issues, rate limits, or invalid requests), Promise.all() immediately rejects and discards all other results — even if they completed successfully.

async function demonstrateFailFast() {
  const tasks = [
    askGPT("What are the top attractions in Paris?"),     // Succeeds
    askGPT("This will cause an API error somehow"),       // Fails!
    askGPT("What are French cultural norms?")             // Succeeds
  ];
  
  try {
    // If the second question fails, you lose ALL results
    const results = await Promise.all(tasks);
  } catch (error) {
    // Only the error is available here - successful results are lost
    console.error("Lost all results due to one failure:", error);
  }
}

This behavior is problematic for real-world GPT-5 workflows. API calls can fail for many reasons:

  • Network connectivity issues.
  • Rate limiting from OpenAI.
  • Invalid prompts or parameters.
  • Temporary service disruptions.
  • Token limit exceeded for one particular request.

If you're researching ten topics and one fails, you still want the other nine answers rather than getting nothing at all.

Promise.allSettled: Robust and Production-Ready

Promise.allSettled() solves the fail-fast problem by waiting for all Promises to complete, regardless of whether they succeed or fail. It returns an array of result objects, each indicating whether its Promise was fulfilled or rejected:

async function robustParallelExample() {
  const tasks = [
    askGPT("What are the top attractions in Paris?"),
    askGPT("How do I get around Paris?"),
    askGPT("What are French cultural norms?")
  ];
  
  // Wait for all Promises to complete (success or failure)
  const results = await Promise.allSettled(tasks);
  
  // Each result has a 'status' field: 'fulfilled' or 'rejected'
  results.forEach((result) => {
    if (result.status === "fulfilled") {
      console.log(`✅ Success: ${result.value}`);
    } else {
      console.error(`❌ Failed: ${result.reason}`);
    }
  });
}

The PromiseSettledResult type has two possible shapes:

  • { status: "fulfilled", value: T } — contains the successful result.
  • { status: "rejected", reason: any } — contains the error that caused rejection.

This structure allows you to handle successes and failures independently, ensuring no valuable results are lost even when some requests fail.

Choosing the Right Promise Method

Use Promise.all() when:

  • All operations must succeed for the workflow to be valid.
  • A single failure should abort the entire process.
  • You're in a controlled environment with high reliability.
  • You want simpler code for tutorial examples.

Use Promise.allSettled() when:

  • Partial results are still valuable (most research/analysis workflows).
  • You need to handle individual failures gracefully.
  • You're building production systems where resilience matters.
  • You want to log which specific requests failed while using successful ones.

For GPT-5 workflows, Promise.allSettled() is almost always the better choice because it ensures you get maximum value from your parallel execution even when some requests encounter issues.

Building a Production-Ready Parallel Workflow

Now let's build a complete parallel workflow that demonstrates both speed and resilience. We'll create a travel planning system that researches multiple independent topics simultaneously and synthesizes them into a comprehensive guide.

Step 1: Create the Research Function

First, let's create an async function that handles individual research questions:

import OpenAI from "openai";

// Initialize the OpenAI client
const client = new OpenAI();

async function askQuestion(question: string): Promise<[string, string]> {
  /**
   * Ask GPT-5 a single research question
   * Returns: [question, answer] tuple for easy matching
   */
  console.log(`🔄 Asking: ${question}`);
  
  // Send async request to GPT-5
  const response = await client.responses.create({
    model: "gpt-5",
    instructions: "You are a travel expert. Give brief, helpful answers.",
    input: [{ role: "user", content: question }],
    reasoning: { effort: "minimal" },
    store: false
  });
  
  // Extract the answer
  const answer = response.output_text;
  
  console.log(`✅ Answered: ${question}`);
  
  return [question, answer];
}

Key design decisions:

  • Return type Promise<[string, string]>: Returns a tuple so we can easily match questions to answers.
  • Console logging: Helps visualize when each question starts and completes during parallel execution.
  • Minimal reasoning: Since these are independent research questions, we don't need deep reasoning yet.
Step 2: Define Independent Research Questions

Next, we'll define the questions that will execute in parallel. These should be independent — none should depend on answers from the others:

// Independent research questions for Paris trip planning
const questions = [
  "What are the top 3 must-see attractions in Paris?",
  "What is the most efficient way to get around Paris as a tourist?",
  "What are important cultural etiquette tips for visitors to France?"
];

These questions cover different aspects of travel planning (attractions, transportation, culture) and can be researched simultaneously without any dependencies.

Step 3: Execute Parallel Research with Robust Error Handling
Step 4: Aggregate Results into Final Synthesis
Step 5: Complete the Workflow
Understanding the Execution Flow

When you run this workflow, you'll observe three distinct phases that demonstrate the power of parallel processing:

Phase 1: Instant Launch (happens immediately)

Starting 3 research questions in parallel...

🔄 Asking: What are the top 3 must-see attractions in Paris?
🔄 Asking: What is the most efficient way to get around Paris as a tourist?
🔄 Asking: What are important cultural etiquette tips for visitors to France?

All three "🔄 Asking" messages appear immediately because the API calls fire off simultaneously, not sequentially.

Phase 2: Concurrent Completion (may arrive in any order)

✅ Answered: What are the top 3 must-see attractions in Paris?
✅ Answered: What are important cultural etiquette tips for visitors to France?
✅ Answered: What is the most efficient way to get around Paris as a tourist?

The "✅ Answered" messages arrive as GPT-5 finishes each response — often in a different order than they were asked. This proves your requests are truly running in parallel, not waiting for each other.

Phase 3: Intelligent Synthesis (sequential aggregation)

Research completed! (3/3 successful)

📝 Synthesizing research into comprehensive guide...

=== Paris Travel Guide ===

[Comprehensive travel guide combining all research...]

All the concurrent research gets woven together into a comprehensive travel guide that combines the speed benefits of parallel processing with thoughtful analysis.

Complete Example Output

Here's what the full workflow output looks like:

Starting 3 research questions in parallel...

🔄 Asking: What are the top 3 must-see attractions in Paris?
🔄 Asking: What is the most efficient way to get around Paris as a tourist?
🔄 Asking: What are important cultural etiquette tips for visitors to France?
✅ Answered: What are the top 3 must-see attractions in Paris?
✅ Answered: What is the most efficient way to get around Paris as a tourist?
✅ Answered: What are important cultural etiquette tips for visitors to France?

Research completed! (3/3 successful)

📝 Synthesizing research into comprehensive guide...

=== Paris Travel Guide ===

Paris Quick Guide: Essentials for Your Visit

TOP ATTRACTIONS
1. Eiffel Tower: Book summit tickets in advance; visit at sunset for stunning views and the evening light show. Best photo spot: Trocadéro Gardens.

2. Louvre Museum: Reserve timed entry online. Prioritize the Denon Wing (Mona Lisa, Winged Victory). The museum is closed Tuesdays. Allow 2-3 hours minimum.

3. Notre-Dame & Île de la Cité: While the cathedral's interior remains closed for restoration, stroll the island and visit Sainte-Chapelle's magnificent stained glass. Book ahead for Sainte-Chapelle.

GETTING AROUND
- Metro/RER: Fastest option. Get a Navigo Easy card (pay-as-you-go) or Navigo Découverte weekly pass (Monday-Sunday).
- Walking: Many attractions are walkable; Paris is made for strolling.
- Buses: Scenic option, same tickets as metro.
- Vélib' bikes: Short-term bike rentals available citywide.
- Apps: RATP app, Citymapper, or Google Maps for real-time directions.
- Avoid rush hours: 8-10 AM and 5-7:30 PM on weekdays.

CULTURAL ETIQUETTE
- Greetings: Always start with "Bonjour/Bonsoir, Madame/Monsieur" before asking questions or requesting help. Then ask "Parlez-vous anglais?"
- Basic French: Learn "s'il vous plaît" (please), "merci" (thank you), "pardon" (excuse me).
- Dining: Keep hands visible on the table (wrists), tear bread rather than cutting it, place it directly on the tablecloth. Service is included; small tips appreciated but not required. Ask for tap water: "une carafe d'eau."
- Public behavior: Keep voices low on public transport and in museums. Dress smart-casual; avoid beachwear or athletic gear in nice restaurants.
- Shopping: Greet shopkeepers when entering and say goodbye when leaving.

This guide covers the essentials for a smooth Paris visit. Enjoy the City of Light!
Performance Benefits and Real-World Impact

This parallel workflow pattern provides dramatic performance improvements while maintaining result quality:

Speed Comparison:

  • Sequential execution: 3 questions × 3-5 seconds each = 9-15 seconds.
  • Parallel execution: ~3-5 seconds total (time of slowest request).
  • Performance gain: 3x faster or more.

Resilience Benefits:

  • Partial results are preserved when individual requests fail.
  • Detailed error logging helps debug specific failures.
  • The travel guide can still be generated with available data.
  • Production systems remain operational even with intermittent API issues.

When to Use This Pattern:

  • Research tasks requiring multiple independent investigations.
  • Data gathering that can be parallelized (market research, competitive analysis).
  • Scenarios where partial results provide value.
  • High-latency operations that benefit from concurrency.
  • Production systems requiring resilience to individual failures.

When to Avoid This Pattern:

  • Questions that depend on previous answers (use sequential chaining instead).
  • Operations that must all succeed or all fail atomically.
  • Rate-limited scenarios where parallel requests cause throttling.
  • Simple single-question workflows where concurrency adds complexity without benefit.
Summary & Practice Preparation

You've mastered production-ready parallel processing patterns that transform slow sequential workflows into lightning-fast concurrent operations. The key insights you've learned:

  1. Asynchronous fundamentals: Understanding Node.js's event loop and non-blocking I/O.
  2. Promise.all vs Promise.allSettled: Knowing when to use fail-fast vs resilient execution.
  3. Two-phase workflow: Parallel data gathering + sequential synthesis.
  4. Error handling: Gracefully managing partial failures in production systems.
  5. TypeScript type safety: Using type guards and proper Promise typing.

The combination of Promise.allSettled() for parallel research gathering and sequential result synthesis provides both speed and quality, making it ideal for complex analysis tasks like travel planning, market research, or technical evaluations.

In the upcoming exercises, you'll apply these patterns to real-world scenarios, handle edge cases, and learn to optimize concurrent GPT-5 workflows for various use cases. Remember: use parallel processing with Promise.allSettled() for independent research tasks where partial results have value, then aggregate results sequentially for comprehensive final analysis.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal