Telegram-iOS Architecture with 100+ Submodules: A Modular Monorepo Deep Dive

Telegram-iOS is built as a Bazel-driven modular monorepo containing over 100 independent submodules, each with isolated Swift/Objective-C sources, public headers, and BUILD files, enabling parallel compilation and fine-grained dependency management between the core engine, feature UI components, and third-party libraries.

The TelegramMessenger/Telegram-iOS repository represents one of the largest open-source iOS codebases, structured as a highly modular monorepo where functionality is partitioned into more than 100 self-contained submodules. This architecture leverages Bazel as its build system to manage complex dependencies between core networking engines, UI feature modules, and native libraries like sqlcipher and rlottie, ensuring deterministic builds and scalable team collaboration.

Bazel-Centric Build System and Workspace Structure

According to the Telegram-iOS source code, the repository root contains a WORKSPACE file and a top-level BUILD.bazel that define external repositories and common build rules. Each submodule ships its own BUILD file declaring Swift/Objective-C sources, resources, and dependencies, allowing Bazel to treat every feature as an independent library.

In submodules/CallListUI/BUILD, the target definition follows this pattern:

swift_library(
    name = "CallListUI",
    srcs = glob(["Sources/**/*.swift"]),
    hdrs = glob(["PublicHeaders/**/*.h"]),
    deps = [
        "//Telegram:TelegramFramework",
        "//submodules/AvatarNode",
        "//submodules/AlertUI",
    ],
    visibility = ["//visibility:public"],
)

This configuration enables deterministic builds and parallel compilation, where Bazel analyzes the dependency graph across all 100+ modules before invoking the compiler.

The 100+ Feature Submodules Organization

The submodules/ directory contains over 100 feature-oriented modules, each following a standardized structure to isolate functionality and prevent circular dependencies. As implemented in TelegramMessenger/Telegram-iOS, the typical submodule layout includes:

  • Sources/ — Swift or Objective-C implementation files
  • PublicHeaders/ — C/Objective-C headers exposed to other modules
  • BUILD — Bazel target definition specifying dependencies and visibility

Representative submodules include:

This micro-feature architecture keeps compile-times manageable and allows separate teams to develop independent pieces without merge conflicts in monolithic files.

Core Application Layer and Entry Points

The Telegram/ folder contains the application entry point, global services, and Xcode project layout generated by Bazel. Key files include:

The app's root controller assembles submodule UI components (such as ChatListController, CallListController, and AvatarNode) and injects the shared AccountContext, which carries the Engine (network, database, media) and PresentationData (theme, strings).

Data Flow: Engine, Presentation, and UI Composition

The architecture enforces strict separation between the engine layer and UI components. According to the source code in submodules/CallListUI/Sources/CallListController.swift, the data flow operates as follows:

  1. Engine Layer — TelegramCore and Telegram frameworks provide low-level APIs for network (MTProto), database (sqlcipher), and media handling (libvpx, opusfile)
  2. Feature Submodules — Import the engine via Bazel deps (e.g., //Telegram:TelegramFramework) and expose Swift-friendly wrappers
  3. Presentation Layer — PresentationData and PresentationResources from TelegramPresentationData pass through AccountContext to provide localization and theming
  4. UI Composition — Each screen is a controller node (ASDisplayNode-based) assembling child nodes from other submodules

The CallListController demonstrates this pattern by receiving AccountContext and delegating actions to context.engine rather than containing networking logic directly:

public final class CallListController: TelegramBaseController {
    private let context: AccountContext
    private var presentationData: PresentationData

    public init(context: AccountContext, mode: CallListControllerMode) {
        self.context = context
        self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
        super.init(context: context,
                   navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData,
                                                                             style: .glass))
    }

    override public func loadDisplayNode() {
        self.displayNode = CallListControllerNode(controller: self,
                                                  context: self.context,
                                                  mode: self.mode,
                                                  presentationData: self.presentationData,
                                                  call: { [weak self] peerId, isVideo in
                                                      self?.call(peerId, isVideo: isVideo)
                                                  })
        self._ready.set(self.controllerNode.ready)
        self.displayNodeDidLoad()
    }
}

This separation keeps each submodule thin, testable, and focused solely on UI presentation.

Third-Party Library Integration as Bazel Targets

Third-party dependencies reside in submodules/ and third-party/, wrapped as Bazel targets for deterministic integration:

  • sqlcipher — Encrypted SQLite database engine at submodules/sqlcipher/Sources/sqlite3.c
  • rlottie — Lottie animation rendering (C++ with Swift bridge) at submodules/rlottie/LottieInstance.mm
  • libvpx — VP8/VP9 video codec at third-party/libvpx
  • opusfile — Audio codec for voice calls at third-party/opusfile

Each library includes a BUILD file declaring appropriate C/C++ rules and exporting Swift module maps when necessary, allowing native code to link cleanly against the Swift UI submodules.

Summary

  • Modular Monorepo — Telegram-iOS organizes functionality into over 100 self-contained submodules under submodules/, each with dedicated Sources/, PublicHeaders/, and BUILD files.
  • Bazel Build System — The WORKSPACE and distributed BUILD files enable parallel compilation, fine-grained dependency management, and deterministic builds across the entire codebase.
  • Three-Tier Structure — The architecture separates Core (Telegram/ app entry), Features (100+ submodules), and Third-Party libraries (third-party/).
  • Clean Data Flow — The AccountContext injects Engine (network/storage) and PresentationData (theme/localization) into UI controllers, keeping business logic isolated from presentation code.
  • Native Integration — C/C++ libraries like sqlcipher and rlottie are wrapped as Bazel targets and consumed by Swift modules through public headers.

Frequently Asked Questions

Why does Telegram-iOS use Bazel instead of standard Xcode builds?

According to the Telegram-iOS repository structure, Bazel provides deterministic builds and fine-grained dependency analysis required to compile over 100 submodules efficiently. Standard Xcode project files become unmaintainable at this scale, whereas Bazel's BUILD files allow parallel compilation and explicit dependency declaration between the core engine, UI features, and third-party libraries.

How are the 100+ submodules organized physically on disk?

The submodules follow a strict directory convention within submodules/<FeatureName>/, containing Sources/ for Swift/Objective-C implementation, PublicHeaders/ for exposed C/Objective-C interfaces, and a BUILD file defining the Bazel target. This structure prevents circular dependencies and enables independent development of features like CallListUI, AvatarNode, and BrowserUI.

What is the role of AccountContext in the Telegram-iOS architecture?

AccountContext serves as the dependency injection container that carries the Engine (networking, database, media handling) and PresentationData (localization, themes) throughout the application. As shown in submodules/CallListUI/Sources/CallListController.swift, UI controllers receive this context in their initializer to access shared services without hard-coding dependencies, enabling testable and modular UI components.

How does Telegram-iOS integrate C libraries like sqlcipher into Swift code?

Third-party C libraries reside in directories like submodules/sqlcipher/ and third-party/libvpx/, each wrapped with a Bazel BUILD file that declares C/C++ compilation rules and exports a Swift module map. This allows Swift code in feature submodules to import and call native functions directly, such as the encrypted database operations provided by sqlcipher at submodules/sqlcipher/Sources/sqlite3.c.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →