Library / SDK
ocsigen/lwt avatar
ocsigen/lwt

Lwt: promises and concurrent I/O for OCaml

OCaml promises and concurrent I/O

796 stars184 forksOCamlMIT

At a glance

What is it?
Lwt is the OCaml concurrency library behind Ocsigen, and its whole design rests on one data type: the promise, a value that will be resolved in the future. Your code stays in a single thread, only the I/O is parallel, and the library ships the syntax extension that makes the code read like sequential OCaml.
Who is it for?
Lwt suits an OCaml team that wants concurrency without locks, that is already using Mirage or Ocsigen, or that needs the same code to run natively and in a browser through js_of_ocaml. It is the wrong choice for a program that is mostly CPU bound, since the model is built around suspending on I/O, and it is the wrong choice if you need fine control over thread scheduling, because the default is deliberately to hide that.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly OCaml, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One data type carries the whole model

Lwt is a concurrent programming library for OCaml, and it is part of the Ocsigen project, with its main documentation hosted at ocsigen.org/lwt.

The project's own summary is the design in one sentence. Lwt provides a single data type: the promise, which is a value that will become resolved in the future. Creating a promise spawns a computation, and when that computation is I/O, Lwt runs it in parallel with your OCaml code.

That is a much smaller surface than most concurrency libraries offer, and it is the reason Lwt is pleasant to use. There is no thread pool to reason about, no future type, no completion queue, no callback hierarchy. A function that does I/O returns a promise, and a promise is composable with other promises.

The library compiles to native code on Linux, macOS, Windows and other systems, and it is also routinely compiled to JavaScript for the front end and for Node by js_of_ocaml. That portability is not incidental: it comes from the fact that the concurrency primitive is a data structure in the program rather than a runtime feature, so the same code compiles to a browser without an event loop shim.

The licence is MIT, with the licence file in the repository as LICENSE.md. The last push was on 2026-10-19, and the recent releases are 6.1.0 from 2026-02-03, 6.0.0 from 2026-01-12 and 5.8.0 from 2024-11-13.

One thread for your code, workers for the I/O

The concurrency model has a property that surprises people coming from thread-based code, and it is stated directly: OCaml code, including creating and waiting on promises, is run in a single thread by default, so you don't have to worry about locking or preemption.

That is the whole reason the promise type is pleasant. In a threaded model, every shared value needs a lock and every lock needs a discipline about ordering. In Lwt, the only thing shared between pieces of your program is the promise, and a promise is resolved once and read many times, so the data race that a lock would be protecting against cannot happen in the first place.

The escape hatch exists, and it is opt-in. You can detach code to be run in separate threads, and the library provides a module for exactly that, Lwt_preemptive, which spawns system threads. That is how a CPU bound piece of work is kept from blocking the event loop, and it is also where the single threaded guarantee ends.

The internals are described in the documentation as a combination of worker threads and non-blocking file descriptors used to resolve in parallel the promises that do I/O. So there is parallelism, it is just not parallelism over your code.

The alternative shape is the one to compare against. If you use real threads in OCaml, you get parallelism over your code and you get the locking problem with it, and the discipline of never touching shared state outside a mutex becomes part of every module you write. Lwt moves the problem: you get a single threaded model for your logic and a runtime that handles the parallelism for the parts that genuinely block.

Writing the code: let%lwt, pick and run

The README's example is worth reading in full, because it shows the three operations you need: creating promises, waiting for them, and forcing the whole thing to run.

The example requests the Google front page and fails if the request is not completed in five seconds:

ocaml
let () =
  let request =
    let%lwt addresses = Lwt_unix.getaddrinfo "google.com" "80" [] in
    let google = Lwt_unix.((List.hd addresses).ai_addr) in

    Lwt_io.(with_connection google (fun (incoming, outgoing) ->
      write outgoing "GET / HTTP/1.1\r\n";%lwt
      write outgoing "Connection: close\r\n\r\n";%lwt
      let%lwt response = read incoming in
      Lwt.return (Some response)))
  in

  let timeout =
    Lwt_unix.sleep 5.;%lwt
    Lwt.return_none
  in

  match Lwt_main.run (Lwt.pick [request; timeout]) with
  | Some response -> print_string response
  | None -> prerr_endline "Request timed out"; exit 1

Functions such as Lwt_io.write create promises, and the let%lwt construct provided by lwt_ppx, or the let* construct provided by the Lwt.Syntax module, is used to wait for a promise to resolve. The code after in is scheduled to run after the code inside the let and in has resolved.

If you are not using the lwt_ppx syntax extension, the same program is written with the let* binding operators from the Lwt.Syntax module instead, which is the alternative shown in the documentation. The two forms are interchangeable in what they express, and the difference is whether your project already takes the PPX dependency.

Two functions finish the job. Lwt.pick races promises against each other and behaves as the first one to complete, which is exactly what the timeout is. And Lwt_main.run forces the whole promise computation network to be executed, which is what actually turns the program into a running request rather than a description of one.

The module family and what each one is for

The overview section of the documentation is a map of the library, and it is organised by what you are trying to do rather than by layer.

The core library Lwt provides the promises, and along with them a few pure OCaml helpers, such as promise-friendly mutexes, condition variables and mvars. These are the primitives for the case where two parts of your program need to coordinate: the mutex and the condition variable exist in the promise world, which is how you build a worker pool or a bounded queue without leaving the model.

There is a big Unix binding, Lwt_unix, that binds almost every Unix system call, and a higher-level module Lwt_io that provides nicer I/O channels. Lwt_process is for subprocess handling, Lwt_preemptive spawns system threads as described earlier, and Lwt_react offers reactive programming on top of the same primitives.

The PPX syntax extension is listed separately in that overview, with the bluntest phrase in the documentation attached to it: it allows using all of the above without going crazy. That is a fair characterisation of what a syntax extension buys you, because the alternative spelling of a promise bind is a match on a constructor, and the alternative spelling of sequencing two I/O operations is a nested let with a function argument.

The repository shows the same spread in the packaging. There are separate opam files for lwt, lwt_ppx, lwt_direct, lwt_runtime_events, lwt_react and lwt_retry, plus a dedicated test package for the ppx let extension, lwt_ppx__ppx_let_tests.opam. The examples directory holds a standalone ppx example, a stall_detection example and a tracing example.

That stall_detection example is worth a second look, because it names the failure mode that promise-based code has: a program that never resolves, and never fails, and therefore hangs.

Installing with libev and opam

The installation section is two steps, and the first one is a system package rather than an opam install.

First, use your system package manager to install a development libev package, which is often called libev-dev or libev-devel. Then:

bash
opam install conf-libev lwt

The conf-libev step is opam's convention for a package that locates a library already provided by the system, and installing it alongside lwt is what tells the build that libev is present. The distinction matters on a machine where the development headers are installed but the opam findlib package is not, which is a common way to get a confusing build failure.

For working on the library itself, the Makefile has the developer commands. The default target builds the project with dune build, the test target runs dune runtest, and there is a dev-deps target that installs the dependencies with opam install . --deps-only --yes. There is also a doc target, dune build @doc, and the comment in the Makefile explains how the themed site published at ocsigen.org/lwt is built from that output using doc/build.sh with wodoc, replacing the former ocamldoc and wikidoc pipeline.

One more target is worth knowing about if you touch the syntax extension. The ppx_let-test target builds and runs the integration test for the let extension, and there is a promote target that copies the expected output files over the recorded ones, which is the standard way of accepting a change in the printed form of the extension's behaviour.

The expected-files list in the Makefile is also informative, since it records which directories are expected to be reproducible and which are excluded, including the Unix configuration directory and a few specific modules.

A manual in transition that still calls promises threads

The documentation section is unusually candid about its own state, and that candour is useful to a reader.

The project says it is currently working on improving the Lwt documentation, drastically, and that the manual is being rewritten. In the meantime it points at the current manual, at a nicely written Lwt tutorial from Mirage, at an example of a simple server written in Lwt, and at Concurrent Programming with Lwt, which it describes as a nice source of Lwt examples, translations of code from the excellent Real World OCaml that are just as useful if you are not reading the book.

Then there is a note that explains a great deal of confusion for newcomers. Much of the current manual refers to 'a Lwt.t as lightweight threads or just threads, and this will be fixed in the new manual. 'a Lwt.t is a promise, and has nothing to do with system or preemptive threads.

That note is the single most useful sentence in the documentation, because the word thread in OCaml carries two other meanings, the system thread and the preemptive thread, and the old manual's usage of it for promises suggests a model that is not what the library does. A promise is a value, not a thread, and treating it as a thread is what leads people to expect preemption.

The community channels are the usual OCaml set: an issue tracker, a Discord chat, discuss.ocaml.org, and Stack Overflow with an ocaml and lwt tag. Release announcements are made on discuss.ocaml.org, and the repository can be watched for Releases only if you want to follow the project without the issue traffic.

For contributors, CONTRIBUTING.md contains tips for working on the code, such as how to check the code out, how review works, and a high-level outline of the code base, and the project says the documentation always needs proofreading and fixes, which is an easy first contribution in a project where the manual is being rewritten anyway.

Editorial conclusion

Lwt suits an OCaml team that wants concurrency without locks, that is already using Mirage or Ocsigen, or that needs the same code to run natively and in a browser through js_of_ocaml. It is the wrong choice for a program that is mostly CPU bound, since the model is built around suspending on I/O, and it is the wrong choice if you need fine control over thread scheduling, because the default is deliberately to hide that. Verify three things before you commit: that the OCaml and opam versions you target are compatible with the 6.x releases, whose opam packages ship together, that your dependency surface is satisfied by libev as the documentation describes, and that you are prepared for the manual to be in transition, since the project says the manual is being rewritten and much of the current text still calls promises threads.

Frequently asked questions

What is Lwt?

It is a concurrent programming library for OCaml, part of the Ocsigen project. It provides a single data type, the promise, a value that will be resolved in the future, and creating a promise spawns a computation, so I/O runs in parallel with your OCaml code while the code itself stays in one thread by default.

How do I install Lwt?

Install a development libev package first with your system package manager, often called libev-dev or libev-devel, and then run opam install conf-libev lwt. The conf-libev package tells the build that the system library is present.

Do I need locks when using Lwt?

No. OCaml code, including creating and waiting on promises, runs in a single thread by default, so you do not have to worry about locking or preemption. You can detach code to separate threads on an opt-in basis, and Lwt_preemptive is the module that spawns system threads.

How do I write concurrent code with Lwt?

You wait for a promise with let%lwt from the lwt_ppx extension, or with the let* operators from the Lwt.Syntax module if you are not using the extension. Lwt.pick races promises and behaves as the first to complete, and Lwt_main.run forces the whole promise computation network to be executed.

Can Lwt code run in a browser?

Lwt compiles to native code on Linux, macOS, Windows and other systems, and it is also routinely compiled to JavaScript for the front end and for Node by js_of_ocaml, which is why the concurrency primitive is a data structure rather than a runtime feature.

Is Lwt still being maintained?

The last push was on 2026-10-19 and recent releases include 6.1.0 from 2026-02-03, 6.0.0 from 2026-01-12 and 5.8.0 from 2024-11-13. The project notes it is working on improving the documentation, and the manual currently in use still refers to promises as lightweight threads.

Official sources

  1. License: MIT
  2. ocsigen/lwt on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ocsigen-lwt.svg)](https://hysenlabs.com/projects/ocsigen-lwt)

Was this useful?