Toy Language Types: Render Types and Signatures, Infer Generic Return Types

Quick Overview

Model the types of a toy programming language, with primitives, generic type variables and nested tuples, render types and function signatures as strings, then infer a call's concrete return type by binding generics consistently across arguments. It tests recursive data modeling, exact formatting and type inference with clear errors.

Toy Language Types: Render Types and Signatures, Infer Generic Return Types

Company: OpenAI

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: hard

Interview Round: Onsite

You are building part of the type checker for a small toy programming language. Its type system has three kinds of types: - **Primitive types**, such as `int`, `float`, `str` and `bool`. - **Generic type variables**, written `T1`, `T2` and so on, which stand for any type. - **Tuple types**: an ordered list of types, which may be nested, such as a tuple of `int` and a tuple of `T1` and `str`. A **function signature** is a list of parameter types and a return type. The exact textual format was not reported. For this exercise, assume a primitive or type variable is written as its name; a tuple is written as its element types separated by `", "` inside square brackets; and a function is written as its parameter types separated by `", "` inside parentheses, followed by `" -> "` and the return type. For example, a function taking an `int` and a tuple of `T1` and `str`, and returning `T1`, is written `(int, [T1, str]) -> T1`. ### Clarifying Questions - Which primitive types exist, and can new ones be added later? - Can a function have zero parameters, and can a tuple be empty? - Can argument types passed to a call themselves contain type variables, or are they always concrete? - If a type variable appears only in the return type, is that an error, or does the result stay generic? - Are function types themselves allowed as parameter or return types? - What should happen on a type mismatch: raise an error, or return a result that describes it? ### Part 1 — Represent types and render them as strings Design classes (or another representation) for the three kinds of types and for function signatures, and implement a `to_str` method (or `__str__`) that renders them exactly in the format above, including nested tuples. ```hint One shape, many cases Treat a type as a tree whose leaves are primitives and type variables; rendering is then a single recursive rule per node kind. ``` #### What This Part Should Cover - A clean, extensible representation of the three type kinds and of function signatures - Recursive rendering that is exactly right for nested tuples and for functions with zero or many parameters - Structural equality, so two separately built but identical types compare as equal ### Part 2 — Infer the return type of a call Implement `infer_return(func, arg_types)`. Given a function signature that may contain type variables, and the concrete types of the arguments in a call, determine what each type variable must be and return the concrete return type. Raise a clear error when the call is invalid: the wrong number of arguments, a primitive mismatch, a tuple of the wrong shape, or one type variable required to be two different types. For example, calling `(int, [T1, str]) -> T1` with arguments `int` and `[float, str]` returns `float`. ```hint Walk both trees together Traverse each parameter type and its argument type in lockstep, recording what each type variable has to be, and check every later occurrence against what you recorded. ``` #### What This Part Should Cover - Consistent binding of type variables across all parameters, including nested positions - Every error case detected with a helpful message - Substitution of the bindings into the return type, including nested and repeated variables - Complexity in terms of the total size of the types ### What a Strong Answer Covers - A data model that keeps rendering and inference simple and makes adding a new type kind easy - Exact output format, verified with tests on nested cases - Correct, well-explained inference with clear errors - Awareness of how the problem grows when arguments may themselves be generic ### Follow-up Questions - Argument types may now contain their own type variables. What changes, and what is an occurs check? - Add function types as first-class values, so a parameter can itself be a function. How do rendering and inference change? - How would you parse the string format back into types? - Add subtyping, so an `int` argument is accepted where a `float` is expected. How does inference change?

Overview: Model the types of a toy programming language, with primitives, generic type variables and nested tuples, render types and function signatures as strings, then infer a call's concrete return type by binding generics consistently across arguments. It tests recursive data modeling, exact formatting and type inference with clear errors.

|Home/Software Engineering Fundamentals/OpenAI
OpenAI logo
OpenAI
Sep 20, 2026
hardSoftware EngineerOnsiteSoftware Engineering Fundamentals
0
0

You are building part of the type checker for a small toy programming language. Its type system has three kinds of types:

  • Primitive types , such as int , float , str and bool .
  • Generic type variables , written T1 , T2 and so on, which stand for any type.
  • Tuple types : an ordered list of types, which may be nested, such as a tuple of int and a tuple of T1 and str .

A function signature is a list of parameter types and a return type.

The exact textual format was not reported. For this exercise, assume a primitive or type variable is written as its name; a tuple is written as its element types separated by ", " inside square brackets; and a function is written as its parameter types separated by ", " inside parentheses, followed by " -> " and the return type. For example, a function taking an int and a tuple of T1 and str, and returning T1, is written (int, [T1, str]) -> T1.

Clarifying Questions Guidance

  • Which primitive types exist, and can new ones be added later?
  • Can a function have zero parameters, and can a tuple be empty?
  • Can argument types passed to a call themselves contain type variables, or are they always concrete?
  • If a type variable appears only in the return type, is that an error, or does the result stay generic?
  • Are function types themselves allowed as parameter or return types?
  • What should happen on a type mismatch: raise an error, or return a result that describes it?

Part 1 — Represent types and render them as strings

Design classes (or another representation) for the three kinds of types and for function signatures, and implement a to_str method (or __str__) that renders them exactly in the format above, including nested tuples.

What This Part Should Cover Guidance

  • A clean, extensible representation of the three type kinds and of function signatures
  • Recursive rendering that is exactly right for nested tuples and for functions with zero or many parameters
  • Structural equality, so two separately built but identical types compare as equal

Part 2 — Infer the return type of a call

Implement infer_return(func, arg_types). Given a function signature that may contain type variables, and the concrete types of the arguments in a call, determine what each type variable must be and return the concrete return type. Raise a clear error when the call is invalid: the wrong number of arguments, a primitive mismatch, a tuple of the wrong shape, or one type variable required to be two different types.

For example, calling (int, [T1, str]) -> T1 with arguments int and [float, str] returns float.

What This Part Should Cover Guidance

  • Consistent binding of type variables across all parameters, including nested positions
  • Every error case detected with a helpful message
  • Substitution of the bindings into the return type, including nested and repeated variables
  • Complexity in terms of the total size of the types

What a Strong Answer Covers Guidance

  • A data model that keeps rendering and inference simple and makes adding a new type kind easy
  • Exact output format, verified with tests on nested cases
  • Correct, well-explained inference with clear errors
  • Awareness of how the problem grows when arguments may themselves be generic

Follow-up Questions Guidance

  • Argument types may now contain their own type variables. What changes, and what is an occurs check?
  • Add function types as first-class values, so a parameter can itself be a function. How do rendering and inference change?
  • How would you parse the string format back into types?
  • Add subtyping, so an int argument is accepted where a float is expected. How does inference change?
Loading comments...