This guide documents what you could call Jane Street’s house style. It’s not an absolute guide to how code is written everywhere here; different groups make different decisions in some cases. We’ll document some of the variations here, while noting which one of those we think of as the house style.

Even though the house style isn’t universally followed, it’s a good default, and it’s the style we use in our most foundational libraries, including Base, Core, Async and Incremental. Indeed, those libraries are generally good places to look at for examples of good practice.


  • Indentation should follow the rules of ocp-indent. This is enforced by default by Jenga, and is included in Jane Street default vim and emacs configs, but you can also achieve it by using ocp-indent with the JaneStreet ruleset.
  • The level of indentation should generally not be determined by the length of a previous line. For example, prefer this:

    let message =
      match result with
      | Ok () -> "ok"
      | Error str -> str

    to this:

    let message = match result with
                  | Ok () -> "ok"
                  | Error str -> str


  • Identifiers (constructors, variables, structures, type names, …) are written using underscores to separate words, not in CamelCase. So write num_apples not numApples and Foo_bar, not FooBar.
  • Identifiers that exist for short scopes should be short, e.g.,

    List.iter ~f:(fun x -> x + 1) numbers

    whereas identifiers that live for large scopes should be larger and more descriptive.


  • Use OCamldoc style comments – starting with (** – in mli’s. Make sure to use square-brackets to enclose small OCaml values, and {[ ]} to enclose larger blocks.
  • Avoid comments that add no useful information to the type and function name. i.e., avoid this:

    (** Compares two strings *)
    val compare : string -> string -> int

    Whereas this would be better.

    (** [compare x y] Compares two strings lexicographically *)
    val compare : string -> string -> int

    If you really can’t find anything useful to add with a comment, it’s acceptable to have a comment that is redundant with the type and name, particularly in broadly-aimed libraries like Base and Core. This reflects the fact that it’s considered gauche in some circles to have functions with no documentation whatsoever. As an example, in the Bytes module, we might have this

    (** [length t] returns the number of bytes in [t]. *)
    val length : t -> int

    even though the content is largely duplicative.


  • 90 characters is the maximum line length
  • Whitespace is good. Prefer a + b * c to a+b*c. This applies in many contexts, as illustrated below.

    (* In your comments, leave a space after '(*' and before '*)'. *)
    let x = 5 in (* space on either side of the equals *)
    val example : t (* type signatures, space on either side of the colon *)
    type t = {
      name : string; (* same as above *)
      age : int
    } [@@deriving sexp]
    (* module and functor definitions *)
    module F (Arg : T) = struct
    (* ...but not functor application *)

    This does not apply to labelled function arguments:

    val map : 'a t -> f:('a -> 'b) -> 'b t
  • In a multi-line construct, put keywords and separators at the beginning of the line. This makes it is easier to see the structure, since we read left-to-right. This rule applies in many places: lists, records, tuples, arrow types, infix expressions.

let/in expressions

  • Append in to the end of one-liners:

    let a = true in
    let b = 34 in
    let c = "asdf" in
  • Give in a separate line for multi-liners:

    let rec loop () =
      ...      (* indented *)
    ...        (* not indented *)

    Some people prefer that right-hand side expressions (e.g., the right-hand side of a let statement or match clause) that span multiple lines always begin indented on a line of their own, rather than starting on the same line as the left-hand side.

  • Avoid and unless required to achieve mutual recursion.

if/then/else expressions

  • There are many accepted ways to write if/then/else expressions; what’s most important is clarity. Avoid things like this, which makes it easy to mistake the false branch for the true branch:

    (* Confusing *)
    if cond then when_true else

    A common accepted style is:

    if cond then
  • When a branch contains multiple statements, be sure to wrap with parentheses or begin/end to avoid errors:

    (* This will raise every time... *)
    if validation_failed then
    (* This will only raise when validation fails *)
    if validation_failed then begin

match/with expressions

  • Put | before the first pattern, e.g.,

    match opt with
    | Some x -> ...
    | None -> ...


  • Single line:

    (1, 2, 3)

    No space after the opening paren or before the closing paren, space after each comma.

  • Multiline:

    ( foo
    , bar
    , baz

    Putting the delimiter at the beginning of the newline. When looking at a large parenthesised expression this makes it easy to distinguish, e.g., function application from tuple construction. Some people put the trailing close parenthesis at the end of the line.

    ( foo
    , bar
    , baz )

    The following style is also acceptable:


    Each element occurs on a line followed by a comma (excluding the last element). The closing paren occurs on a line by itself.


  • Single line

    [1; 2; 3]

    No space after the opening bracket or before the closing bracket, space after each semicolon.

  • Multiline

    (* One of these *)
    [ 1
    ; 2
    ; 3
    [ 1
    ; 2
    ; 3 ]
    [ 1;


  • Single line

    { foo = 1; bar = 2; baz = 3 }
    { Foo. foo = 1; bar = 2; baz = 3 }

    Space after the opening brace, space after each semicolon, and space before the closing brace. If the record needs a module constructor, write it first with a space after the period.

  • Multiline

    { foo = 1
    ; bar = 2
    { foo = 1
    ; bar = 2 }
    { Foo.
      bar = 1
    ; baz = 2
    { Foo.
      bar = 1
    ; baz = 2 }

    Or alternately:

    { foo = 1;
      bar = 2;
    { Foo.
      bar = 1;
      baz = 2;

    The opening brace is followed by a space and then the first field or the module name. Each field occurs on a line, there is a space on each side of the equals. The line is either terminated by a semicolon (including the last field), or started with a semicolon and then a space. The closing brace can occur on a line by itself or at the end of the line.

    Do not duplicate the module name in each field. I.e., don’t write

    { = 1;
      Foo.baz = 2;

Other multiline constructs

Multiline infixes should put the separator at the beginning of a line. Write this:

&& test2

rather than:

test1 &&

Similarly for other multiline things with separators, e.g.,

val foo                             val foo :
  :  a              rather            a ->
  -> b               than             b ->
  -> c                                c

Returning unit

print a;
print b;

instead of:

let () = print a in
let () = print b in

Order of labeled arguments

In general, put the short argument first if one is multi-line. For example:

List.iter orders ~f:(fun order ->
  Order.print order;
  Order.defrobulate order;


List.iter ~f:(fun order ->
  Order.print order;
  Order.defrobulate order;
) orders;


When programming with monadic and applicatives, the use of let%bind is generally preferred in cases where an explicit variable is bound. For example, prefer this:

let%bind x = foo y in
let%bind z = bar x in
return x + z


foo y >>= fun x ->
bar x >>= fun z ->
return x + z

That said, even when using let%bind or let%map, infix operators are still useful when operating in a point-free style, i.e., when not binding variables. So, we might write.

let%bind x = m >>| Model.x in
foo x

rather than

let%bind x = (let%map m = m in Model.x m) in
foo x

Boolean-returning functions should have predicates for names

For instance, is_valid is better than check_validity.

Multi-line top-level statements should be terminated with double-semicolons (;;)

Opening Modules


Only open modules with a clear and standard interface, and open all such modules before defining anything else, e.g.,

open Core
open Async

let snoo () = ...


let now () = ... (* shadowed below by opening Time *)

open Core
open Time

Local “open”

Even using a local “open” will pollute the namespace of an expression and risk silently shadowing a variable of the same type. When you do use a local-open, you should aim to keep the scope small. This notation:

let f g =
  Time.(now () < lockout_time)

is generally preferable to this one:

let f g =
  let open Time in
  now () < lockout_time

because the scope is more clearly delimited.

That said, when the interface being opened has a standard and widely understood API, then the let open syntax is preferred.

let open Option.Monad_infix in
load_config () >>= create

Some modules provide an Infix module or an O module which is specifically designed for local opens.


  • Most modules should contain a single type named t. For instance, the String modules defines String.t. When you’re reading the String interface and you see t, think ‘string’ in your head.

  • Prefer functions that return explicit options (or errors) over throwing exceptions. If your function routinely raises an exception, put _exn in the name. For example:

    val create : string -> t option
    val create_exn : string -> t
  • Functions in a module M should typically take M.t as their first argument.

    An exception to this is that optional arguments should be placed before the M.t argument if that is the sole positional argument, to allow the optional arguments to be erased.

  • Prefer standard signature includes to hand-written interfaces. E.g., prefer this:

    include Comparable.S with type t := t

    over this:

    val t_of_sexp : Sexp.t -> t
    val sexp_of_t : t -> Sexp.t
  • Most comments should be for users of a module, not implementers, which means that most comments should be in the mli. We place comments above the function signature, module, or record field described. Small comments can also go to the right of a line of code.

Defensive Programming

  • Always annotate the type of ignored values. That way the compiler will complain if the type changes. For example, imagine what happens to

    ignore (Unix.wait `Any);


    val wait : [`Any | `Pid of Pid.t] -> Pid.t

    changes to return the exit code instead of raising on non-zero:

    val wait : [`Any | `Pid of Pid.t] -> Pid.t * Exit.t

    It’s better to write:

    ignore (Unix.wait `Any : Pid.t);

    The same logic applies to underscores, except where the type is more-or-less pinned down anyway.

  • If a function takes two arguments of the same type and the arguments are used differently, they should usually be labeled to avoid accidental permutation. Notable exceptions include List.append and (-) where the order is sufficiently clear. For example:

    val send : source:User.t -> dest:User.t -> unit
  • Avoid catch-all cases in pattern matches. For example, prefer this:

    let position_change = function
      | Execution e -> Dir.sign (Execution.dir e) * Execution.size e
      | Ack _ | Out _ | Reject _ -> 0

    to this.

    let position_change = function
      | Execution e -> Dir.sign (Execution.dir e) * Execution.size e
      | _ -> 0

    Both are correct, but the former will produce an error if Correction is added to the type being matched on, and the latter won’t.

  • Optional arguments should typically only be used for functions that are called in many different places. That’s because optional arguments make your code less explicit, which makes the call sites harder to understand, and it makes it easy to forget to specify the argument in a case where it’s required.

    A good rule of thumb is to avoid optional arguments for functions that are not exposed in your module interface.

Directory Names

Use dashes (“-”) in for multi-word directory names, instead of underscores (“_”). Thus:


instead of:



It is OK for a function to raise an exception in an exceptional circumstance. We often, but not always, suffix such functions with _exn. Although raising is fine, one should not write code that depends on which exception is raised. That is, one should never declare exceptions or match on them. Doing so is problematic in a large code base because the type system doesn’t track which exception is raised. If callers of a function need to discriminate among error cases, then the function should return a variant distinguishing the cases.

Instead of declaring new exceptions, one should use a function that implicitly constructs an exception, e.g.:

raise_s [%message "something bad happened" (t : t)]


Test your code. Some codebases do better with having unit tests for every function, some are better with end-to-end tests, but generally speaking all significant changes should have tests demonstrating their effect.

Tests can sometimes go in the same file, but typically should go in a separate test library. This is preferable for a number of reasons: For one thing, it encourages you to write tests against the exposed API of your code, which is usually the right approach. It also allows you to draw on more dependencies in your test code than you might want to link in to your production code.

Expect tests are the preferred way of writing tests. This doesn’t mean that you shouldn’t use Quickcheck or other forms of property tests; it’s just that let%expect_test is a single umbrella under which you can write all of these kinds of tests conveniently.

Private submodules

A number of modules expose a Private submodule. User code should not use functions in a Private submodule. Private submodules contain functionality that is internal to the implementation, intended for use in closely-related code like expects tests and benchmarks of the module itself. Such code is often in another library and needs access to private internals of the main module.