[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"academy-blogs-en-1-1-all-golang-the-series-ep148-handling-ai-streams-go-channels-all--*":3,"academy-blog-translations-daytv9s90w0e91h":89},{"data":4,"page":75,"perPage":75,"totalItems":75,"totalPages":75},[5],{"alt":6,"collectionId":7,"collectionName":8,"content":9,"cover_image":10,"cover_image_path":11,"created":12,"created_by":13,"expand":14,"id":83,"keywords":84,"locale":56,"published_at":85,"scheduled_at":13,"school_blog":79,"short_description":86,"status":77,"title":87,"updated":88,"updated_by":13,"slug":80,"views":82},"Implementing Real-time AI Streaming using Go Channels and Goroutines","sclblg987654321","school_blog_translations","\u003Cp>Welcome to EP.148! In our previous episode, we learned how to receive 100% accurate Structured JSON data. However, in real-world applications, we often encounter a major roadblock: Latency (slowness). The more we ask the AI to analyze, the longer it takes to process. Waiting for a massive JSON chunk to complete can take several seconds, leaving users wondering if the system has frozen or the app has crashed.\u003C\u002Fp>\u003Cp>Today, we are going to eliminate this bottleneck by leveraging Go's absolute superpowers—Channels and Concurrency. We will manage a Streaming system that transmits data in tiny pieces called Chunks. This allows responses to render on the screen in real-time the moment the AI generates its very first word, delivering that smooth, interactive experience we all love in ChatGPT.\u003C\u002Fp>\u003Ch2>Why Use Go Channels to Handle Streams?\u003C\u002Fh2>\u003Cp>When you enable stream mode with an AI API (whether it's OpenAI or Ollama), the data transmission behavior changes completely. Instead of waiting for a single, massive payload, the API breaks the response down into \u003Cstrong>Chunks\u003C\u002Fstrong>—tiny fragments of text that continuously flow through the network connection.\u003C\u002Fp>\u003Cp>Using Go Channels as an intermediary pipe to receive and forward these small data packets offers 3 distinct advantages over writing a standard, monolithic loop:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>Decoupling (Separation of Concerns):\u003C\u002Fstrong> Channels allow us to cleanly separate responsibilities. One side is solely responsible for \"fetching data from the AI,\" while the other side only cares about \"rendering data for the user.\" Neither side needs to know the internal workings of the other; they communicate exclusively through the channel.\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>Non-blocking Execution:\u003C\u002Fstrong> Go can immediately process chunk #1 (e.g., sending it to be displayed on the screen) while simultaneously waiting for chunk #2 to arrive over the network via concurrent execution. This completely eliminates idle waiting time, keeping the system highly efficient.\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>Type Safety:\u003C\u002Fstrong> We define exactly what data type our channel accepts (such as \u003Ccode>chan string\u003C\u002Fcode> or \u003Ccode>chan MyStruct\u003C\u002Fcode>). This ensures that whatever data flows through the pipeline always conforms strictly to our designed schema, minimizing runtime errors.\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>💡 Insight for Gophers:\u003C\u002Fh3>\u003Cp>Think of using a channel in this scenario as building a conveyor belt in a factory. The AI API is the machine at the starting line, dropping items onto the belt one by one, and your UI is the worker at the end, picking them up to display. Without this conveyor belt (Channel), you would have to wait for the machine to finish manufacturing \u003Cem>every single item\u003C\u002Fem> before walking over to pick them up all at once—and that is exactly why standard systems feel so slow!\u003C\u002Fp>\u003Ch2>Go Streaming Structure\u003C\u002Fh2>\u003Cp>We will design a function that acts as a Producer. It returns a Receive-only Channel (\u003Cstrong>\u003Ccode>&lt;-chan\u003C\u002Fcode>\u003C\u002Fstrong>), which is like handing over a pipe that can only be used to listen. This design prevents the consumer from accidentally sending data back into the pipe, ensuring a one-way, clean data flow.\u003C\u002Fp>\u003Ch3>Implementation Example:\u003C\u002Fh3>\u003Cp>Go\u003C\u002Fp>\u003Cpre>\u003Ccode>\u002F\u002F Returns a &lt;-chan to signify: \"This pipe is for receiving data only\"\nfunc StreamAIResponse(ctx context.Context, client *openai.Client, prompt string) &lt;-chan string {\n    out := make(chan string)\n\n    \u002F\u002F Run a separate Goroutine so we don't block the main function execution\n    go func() {\n        \u002F\u002F Crucial: Always close the channel when finished to tell the receiver \"no more data\"\n        defer close(out) \n        \n        req := openai.ChatCompletionRequest{\n            Model:  openai.GPT4o,\n            Stream: true, \u002F\u002F The game changer: Enable streaming to let the API spit out chunks\n            Messages: []openai.ChatCompletionMessage{\n                {Role: \"user\", Content: prompt},\n            },\n        }\n\n        stream, err := client.CreateChatCompletionStream(ctx, req)\n        if err != nil {\n            \u002F\u002F In production, consider sending the error through the channel or logging it\n            return \n        }\n        defer stream.Close()\n\n        for {\n            response, err := stream.Recv()\n            \u002F\u002F io.EOF is the signal that the AI has finished its sentence\n            if errors.Is(err, io.EOF) {\n                return \n            }\n            if err != nil {\n                return\n            }\n            \n            \u002F\u002F Immediately push the \"Delta\" (newly received fragment) into the pipe\n            if len(response.Choices) &gt; 0 {\n                out &lt;- response.Choices[0].Delta.Content\n            }\n        }\n    }()\n\n    return out\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3>🔍 A Deep Dive into the Code:\u003C\u002Fh3>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>\u003Ccode>go func() { ... }()\u003C\u002Fcode>\u003C\u002Fstrong>: We offload the API data listening to the background (Goroutine). This allows the \u003Ccode>StreamAIResponse\u003C\u002Fcode> function to return the channel to the user immediately, without having to wait for the AI to finish its entire response.\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>\u003Ccode>defer close(out)\u003C\u002Fcode>\u003C\u002Fstrong>: This is essential channel etiquette. If we don't close the pipe, the receiver waiting for data (using a \u003Ccode>range\u003C\u002Fcode> loop) will wait forever, leading to a \u003Cstrong>Deadlock\u003C\u002Fstrong>.\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>\u003Ccode>stream.Recv()\u003C\u002Fcode>\u003C\u002Fstrong>: This function blocks execution \u003Cem>inside the Goroutine\u003C\u002Fem> while waiting for the next data fragment from the AI. Once a chunk arrives, we quickly shove it into the pipe using \u003Ccode>out &lt;-\u003C\u002Fcode>.\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>Consumption (Usage)\u003C\u002Fh2>\u003Cp>At the \u003Cstrong>Main\u003C\u002Fstrong> or \u003Cstrong>Controller\u003C\u002Fstrong> level (the end of the pipe), we don't need to worry about API complexity or managing Goroutines. Our only job is to wait for the data flowing out of the channel using the \u003Ccode>for range\u003C\u002Fcode> keyword.\u003C\u002Fp>\u003Ch3>Usage Example:\u003C\u002Fh3>\u003Cp>Go\u003C\u002Fp>\u003Cpre>\u003Ccode>\u002F\u002F 1. Call the function and receive the Channel\nresponseChan := StreamAIResponse(ctx, client, \"Explain Go Channels to me\")\n\n\u002F\u002F 2. Use range to pull data from the pipe until close() is called\nfor msg := range responseChan {\n    \u002F\u002F 3. Display each character\u002Fword immediately as it arrives\n    fmt.Print(msg) \n}\n\nfmt.Println(\"\\n--- Streaming Finished ---\")\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3>Why is this approach excellent?\u003C\u002Fh3>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>Smooth UX:\u003C\u002Fstrong> The text doesn't stutter or arrive in a massive block. Instead, it appears gradually with a \"Typing Effect,\" significantly reducing user frustration during long AI processing times.\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>Memory Efficiency:\u003C\u002Fstrong> We don't have to store long strings in memory until the process finishes. We process (in this case, print) the data bit by bit as it arrives.\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>Automatic Termination:\u003C\u002Fstrong> When the background Goroutine finishes and calls \u003Ccode>close(out)\u003C\u002Fcode>, the \u003Ccode>for range\u003C\u002Fcode> loop exits gracefully and safely without us having to write complex break conditions.\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>Critical Precautions for Streaming\u003C\u002Fh2>\u003Cp>While Go handles concurrency beautifully, developers must be mindful of two key areas to ensure system safety:\u003C\u002Fp>\u003Ch3>1. Context Cancellation\u003C\u002Fh3>\u003Cp>This is the most common mistake! If a user clicks Stop or closes their browser while the AI is mid-sentence, and you haven't passed the \u003Ccode>ctx\u003C\u002Fcode> (Context) into \u003Ccode>CreateChatCompletionStream\u003C\u002Fcode>, your backend will continue running the loop and receiving data until the AI finishes.\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>The Consequences:\u003C\u002Fstrong> Wasted tokens (losing money!) and high server load for results no one is watching.\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>The Solution:\u003C\u002Fstrong> Always verify that the \u003Ccode>ctx\u003C\u002Fcode> is still active and ensure \u003Ccode>stream.Close()\u003C\u002Fcode> is triggered immediately upon context cancellation.\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>2. Buffer Size &amp; Backpressure\u003C\u002Fh3>\u003Cp>Typically, for chat applications, we use an \u003Cstrong>Unbuffered Channel\u003C\u002Fstrong> (size 0) to ensure data freshness. We want the information to flow from the API to the user's screen instantly without sitting in a queue.\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>When to use a Buffered Channel?:\u003C\u002Fstrong> If the \"Consumer\" (receiver) processes data slower than the sender (e.g., if you need to run data through a filter before displaying it), using a \u003Cstrong>Buffered Channel\u003C\u002Fstrong> (e.g., \u003Ccode>make(chan string, 10)\u003C\u002Fcode>) allows the sender to keep moving without waiting for the receiver, reducing stream stuttering.\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>💡 Rule of Thumb for Gophers:\u003C\u002Fh3>\u003Cblockquote>\u003Cp>\u003Cstrong>\"If you open it, you must close it.\"\u003C\u002Fstrong> — Every time you use \u003Ccode>go func\u003C\u002Fcode> and \u003Ccode>channel\u003C\u002Fcode>, you must be able to answer: \u003Cem>When will this Goroutine end?\u003C\u002Fem> and \u003Cem>When will this channel be closed?\u003C\u002Fem> If you can't answer those, you likely have a \u003Cstrong>Goroutine Leak\u003C\u002Fstrong>, which will slowly consume your server's memory.\u003C\u002Fp>\u003C\u002Fblockquote>\u003Ch2>Daily Mission\u003C\u002Fh2>\u003Cp>To deepen your understanding of data flow, let's upgrade our stream function to a professional level. Instead of sending raw strings, we will transmit a rich context through the channel.\u003C\u002Fp>\u003Cp>\u003Cstrong>Challenge:\u003C\u002Fstrong> Create a data structure and refactor your function to communicate using this struct:\u003C\u002Fp>\u003Cp>Go\u003C\u002Fp>\u003Cpre>\u003Ccode>type StreamResponse struct {\n    Content      string\n    FinishReason string\n    Err          error\n}\n\n\u002F\u002F Homework: Refactor StreamAIResponse to return &lt;-chan StreamResponse\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3>Food for Thought:\u003C\u002Fh3>\u003Cp>Try to \"Mental Benchmark\" these two scenarios:\u003C\u002Fp>\u003Col>\u003Cli>\u003Cp>\u003Cstrong>Standard Mode:\u003C\u002Fstrong> A 5-second wait, then a massive wall of text appears all at once.\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>Streaming Mode:\u003C\u002Fstrong> A 0.5-second wait, and the first word starts \"typing\" out until it finishes at the 5-second mark.\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Fol>\u003Cp>In which scenario is a user more likely to hit Refresh or close the app? This is \u003Cstrong>Perceived Performance\u003C\u002Fstrong>—the speed the user \u003Cem>feels\u003C\u002Fem>. In the world of UX, the feeling of speed is just as critical as the actual execution time of your code.\u003C\u002Fp>\u003Cdiv data-type=\"horizontalRule\">\u003Chr>\u003C\u002Fdiv>\u003Ch2>Conclusion\u003C\u002Fh2>\u003Cp>Implementing \u003Cstrong>Streaming\u003C\u002Fstrong> with \u003Cstrong>Go Channels\u003C\u002Fstrong> transforms your application from a \"frozen\" interface into a living, breathing experience. Go's clean concurrency model allows us to turn complex real-time logic into something simple, readable, and safe.\u003C\u002Fp>\u003Cp>But with great speed comes great responsibility...\u003C\u002Fp>\u003Ch3>Coming Up Next | EP.149:\u003C\u002Fh3>\u003Cp>Now that your system is smooth and your users are happy, there’s one thing that might make \u003Cem>you\u003C\u002Fem> unhappy: the API bill at the end of the month! We’re diving deep into budget control in \u003Cstrong>\"Token Management: How to Count Tokens and Calculate API Costs on the Backend.\"\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>We'll explore how AI sees our text as numbers and how we can \"predict\" the cost before hitting that API. If you don't want a heart attack when your invoice arrives... don't miss this one!\u003C\u002Fp>\u003Cp>\u003Cstrong>See you in the next episode, Gophers!\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>\u003Cstrong>Follow Superdev Academy on all platforms:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>🔵 Facebook: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.facebook.com\u002Fsuperdev.academy.th\">\u003Cstrong>Superdev Academy Thailand\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🎬 YouTube: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.youtube.com\u002F@SuperdevAcademy\">\u003Cstrong>Superdev Academy Channel\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>📸 Instagram: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.instagram.com\u002Fsuperdevacademy\u002F\">\u003Cstrong>@superdevacademy\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🎬 TikTok: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.tiktok.com\u002F@superdevacademy?lang=th-TH\">\u003Cstrong>@superdevacademy\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🌐 Website: \u003C\u002Fstrong>\u003Ca rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fsuperdevacademy.com\">\u003Cstrong>superdevacademy.com\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003C\u002Fp>","16yhujbtlv83_1m0mesfty8.png","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclblg987654321\u002Fbouypbbx1bom1ke\u002F16yhujbtlv83_1m0mesfty8.png","2026-05-19 09:16:18.590Z","",{"keywords":15,"locale":50,"school_blog":60},[16,22,27,32,37,41,46],{"collectionId":17,"collectionName":18,"created":19,"created_by":13,"id":20,"name":21,"updated":19,"updated_by":13},"sclkey987654321","school_keywords","2026-05-19 09:09:15.823Z","fbj34lco59k2lc0","Go Channels",{"collectionId":17,"collectionName":18,"created":23,"created_by":13,"id":24,"name":25,"updated":26,"updated_by":13},"2026-03-04 08:20:11.547Z","ey3puyme01a9bsw","Go","2026-04-10 16:07:25.893Z",{"collectionId":17,"collectionName":18,"created":28,"created_by":13,"id":29,"name":30,"updated":31,"updated_by":13},"2026-03-04 08:20:14.253Z","ah6lvy4x8qe08l5","Golang","2026-04-10 16:07:26.172Z",{"collectionId":17,"collectionName":18,"created":33,"created_by":13,"id":34,"name":35,"updated":36,"updated_by":13},"2026-03-04 08:24:48.143Z","dourw0uuydrrh1h","Concurrency","2026-04-10 16:07:30.157Z",{"collectionId":17,"collectionName":18,"created":38,"created_by":13,"id":39,"name":40,"updated":38,"updated_by":13},"2026-05-19 09:09:41.882Z","rx514ns8m8ei8tl","AI Streaming",{"collectionId":17,"collectionName":18,"created":42,"created_by":13,"id":43,"name":44,"updated":45,"updated_by":13},"2026-03-04 08:33:58.044Z","nb6p1r8sfqlsxf8","Goroutines","2026-04-10 16:08:04.493Z",{"collectionId":17,"collectionName":18,"created":47,"created_by":13,"id":48,"name":49,"updated":47,"updated_by":13},"2026-05-19 09:09:59.339Z","qic0tqri4x8tvnf","Backend Latency",{"code":51,"collectionId":52,"collectionName":53,"created":54,"flag":55,"id":56,"is_default":57,"label":58,"updated":59},"en","pbc_1989393366","locales","2026-01-22 11:00:02.726Z","twemoji:flag-united-states","qv9c1llfov2d88z",false,"English","2026-04-10 15:42:46.825Z",{"category":61,"collectionId":62,"collectionName":63,"created":64,"expand":65,"id":79,"slug":80,"updated":81,"views":82},"wqxt7ag2gn7xcmk","pbc_2105096300","school_blogs","2026-05-19 09:10:01.807Z",{"category":66},{"blogIds":67,"collectionId":68,"collectionName":69,"created":70,"created_by":13,"id":61,"image":71,"image_alt":13,"image_path":72,"label":73,"name":74,"priority":75,"publish_at":76,"scheduled_at":13,"status":77,"updated":78,"updated_by":13},[],"sclcatblg987654321","school_category_blogs","2026-03-04 08:33:53.210Z","59ty92ns80w_15oc1implw.png","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclcatblg987654321\u002Fwqxt7ag2gn7xcmk\u002F59ty92ns80w_15oc1implw.png",{"en":74,"th":74},"Golang The Series",1,"2026-03-16 04:39:38.440Z","published","2026-04-25 02:32:15.470Z","daytv9s90w0e91h","golang-the-series-ep148-handling-ai-streams-go-channels","2026-06-02 01:24:13.020Z",107,"bouypbbx1bom1ke",[20,24,29,34,39,43,48],"2026-06-02 01:24:13.309Z","Eliminate AI latency with Streaming! Learn to use Go Channels and Concurrency to process AI response chunks in real-time, improving UX and perceived performance for your backend.","Golang The Series EP.148: Handling Streams - Building Real-time Chat with Go Channels","2026-06-02 01:24:13.310Z",{"th":80,"en":80}]