Submodules & Monorepos

Reviewed & published by Brayan K

Sometimes one repo needs to contain another. By the end of this lesson you'll add a submodule, understand why fresh clones leave it empty, update it safely, and know when a monorepo is the simpler choice instead.

Part of the free Git course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1️⃣ Adding a Submodule

git submodule add <url> <path> clones another repo into your project and registers it. Git creates a .gitmodules file (the path + URL, which you commit and share) and a special gitlink at the path that pins one exact commit.

# A submodule embeds ANOTHER Git repo inside yours, pinned to one commit.
# Useful for sharing a library across projects without copy-pasting code.

# Add a library repo into libs/dep (clones it and registers it)
git submodule add https://github.com/acme/dep.git libs/dep

# Git created two things to commit:
#   .gitmodules   -> records the path + URL (you commit and share this)
#   libs/dep      -> a "gitlink" pinning the exact commit you got
git status --short

2️⃣ What Gets Committed

The parent stores a pointer to one specific commit of the submodule — not a copy of its files. That's the whole point: every teammate checks out the same version. Notice the 160000 mode in the commit output — that's Git's marker for a gitlink.

# What .gitmodules looks like (this file IS committed and shared):

# [submodule "libs/dep"]
#     path = libs/dep
#     url = https://github.com/acme/dep.git

# Commit both the .gitmodules file and the pinned pointer:
git commit -m "Add dep as a submodule"

# IMPORTANT: the parent repo stores a POINTER to one specific commit of
# dep — not a copy of its files. That's what guarantees every teammate
# checks out the exact same version of the library.

3️⃣ The Empty-Folder Surprise

The most common submodule confusion: a plain git clone leaves submodule folders empty, because their contents aren't fetched by default. Fix it with git submodule update --init --recursive, or avoid it entirely by cloning with --recurse-submodules.

# THE classic gotcha: a normal clone leaves submodule folders EMPTY,
# because their contents aren't fetched by default.

git clone https://github.com/acme/app.git
ls app/libs/dep        # 😱 empty!

# Fix it — initialise and fetch the submodule contents:
git submodule update --init --recursive

# Better: clone everything in one shot next time:
git clone --recurse-submodules https://github.com/acme/app.git

Your turn. Add a submodule and make a fresh clone fill it in. Fill in the two blanks.

# 🎯 YOUR TURN — replace each ___ then run it in a practice repo.
# Goal: add a submodule, then make a fresh clone populate it.

# 1) Add a library repo at libs/dep
git submodule ___ https://github.com/acme/dep.git libs/dep
#                👆 the subcommand that adds a submodule

# 2) Commit the .gitmodules file and the pinned pointer
git commit -m "Add dep submodule"

# 3) Someone clones the project — make their empty submodule folder fill in
git submodule update ___ --recursive
#                        👆 the flag that initialises before updating

# ✅ Expected: after step 3, libs/dep contains the library's files.

4️⃣ Updating a Submodule

To move a submodule forward, update it inside its folder, then commit the changed pointer in the parent. Forgetting that second commit (or to push the submodule's own new commits) is the classic way teammates end up on the wrong version.

# Updating a submodule to a newer upstream commit:

# 1) Go into the submodule and pull its latest
cd libs/dep
git fetch
git checkout main
git pull

# 2) Back in the PARENT, the pointer now shows as changed — commit it
cd ../..
git add libs/dep
git commit -m "Bump dep to latest"

# Pull a repo AND advance its submodules in one go:
git pull --recurse-submodules

5️⃣ Submodules vs. Monorepos

A monorepo keeps multiple projects in one repository, so tightly-coupled code is versioned and changed together in a single atomic commit — ideal when cross-cutting changes are common. Submodules keep each project independent, linked by commit pointers — better when components have separate owners, lifecycles, or release schedules. Because submodules add ceremony and footguns, many teams reach for a monorepo (or a package manager) unless they truly need separate repositories.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference

CommandPurpose
git submodule add <url> <path>Embed another repo
git submodule update --init --recursivePopulate submodules after clone
git clone --recurse-submodules <url>Clone parent + submodules
git submodule statusShow pinned submodule commits
git pull --recurse-submodulesPull and advance submodules
git submodule deinit <path>Start removing a submodule

Mini-Challenge: Share a Library Two Ways

No commands given this time — just the plan. Embed a library as a submodule, prove the empty-folder fix, and weigh up a monorepo.

# 🎯 MINI-CHALLENGE: share a library two ways.
# No commands given — just the plan. Use two practice repos.
#
# 1. Create a small "library" repo with one file, and commit it
# 2. In a separate "app" repo, add the library as a submodule at libs/lib
# 3. Commit the .gitmodules file and the pointer
# 4. Clone the app fresh and confirm libs/lib is EMPTY at first
# 5. Run "git submodule update --init --recursive" and confirm it fills in
# 6. Reflect: would a monorepo (both projects in one repo) be simpler here?
#
# ✅ Expected: you can populate the submodule on a fresh clone, and you can
#    explain when a monorepo would have avoided the extra steps.

# your commands here

🎉 Lesson Complete!

Practice quiz

What is a Git submodule?

  • A branch inside a folder
  • A type of merge
  • A separate Git repository embedded at a fixed commit inside a parent repo
  • A compressed archive

Answer: A separate Git repository embedded at a fixed commit inside a parent repo. A submodule is another repo nested in yours, pinned to a specific commit rather than copied.

Which command adds a submodule?

  • git submodule add <url> <path>
  • git add submodule <url>
  • git clone --sub <url>
  • git module add <url>

Answer: git submodule add <url> <path>. git submodule add <url> <path> registers and clones the submodule into the given path.

What file records a repo's submodules?

  • .gitignore
  • .submodules
  • .git/config only
  • .gitmodules

Answer: .gitmodules. The .gitmodules file (committed to the repo) lists each submodule's path and URL.

What exactly does the parent repo store for a submodule?

  • A full copy of the files
  • A pointer to one specific commit of the submodule
  • Just the URL
  • Nothing

Answer: A pointer to one specific commit of the submodule. The parent pins the submodule to an exact commit (a 'gitlink'), so everyone gets the same version.

After cloning a repo with submodules, why are the submodule folders empty?

  • Submodule contents aren't fetched by default — you must init/update them
  • A bug
  • They were deleted
  • They need a rebuild

Answer: Submodule contents aren't fetched by default — you must init/update them. A plain clone leaves submodules uninitialized; their files arrive only after init/update.

Which command initializes and fetches submodule contents after a clone?

  • git submodule fetch
  • git pull --sub
  • git submodule init && git submodule update
  • git checkout --sub

Answer: git submodule init && git submodule update. git submodule update --init (optionally --recursive) fills in the submodule files.

How do you clone a repo AND its submodules in one step?

  • git clone --all
  • git clone --recurse-submodules <url>
  • git clone --deep
  • git clone --modules

Answer: git clone --recurse-submodules <url>. git clone --recurse-submodules clones the parent and populates submodules together.

To move a submodule to its latest upstream commit, you...

  • delete and re-add it
  • run git merge
  • edit .gitmodules
  • cd into it, pull, then commit the new pointer in the parent

Answer: cd into it, pull, then commit the new pointer in the parent. You update the submodule, then commit the changed pointer in the parent so the new commit is recorded.

When is a monorepo often preferred over submodules?

  • Never
  • When tightly-coupled code should be versioned and changed together atomically
  • Only for tiny repos
  • Only for binaries

Answer: When tightly-coupled code should be versioned and changed together atomically. A monorepo keeps interdependent projects in one repo so cross-cutting changes land in a single commit.

A common pain point of submodules is...

  • They run too fast
  • They can't be cloned
  • It's easy to forget to commit/push the updated submodule pointer
  • They delete the parent

Answer: It's easy to forget to commit/push the updated submodule pointer. Forgetting to commit the moved pointer (or to push the submodule) leaves teammates on the wrong commit.

Continue this course

Frequently asked questions

What is a submodule, in plain terms?

It's a way to embed one Git repository inside another. The parent repo doesn't copy the other project's files — instead it stores a pointer to one exact commit of that other repo (a 'gitlink'), plus a .gitmodules file recording the path and URL. This pins every collaborator to the same version of the embedded code, which is great for sharing a library across several projects.

Why are my submodule folders empty after cloning?

Because a normal git clone does not fetch submodule contents by default — it only records that they should exist. Run git submodule update --init --recursive to download and check them out, or clone with git clone --recurse-submodules next time to do it all at once. This 'empty folder' surprise is the single most common submodule confusion.

How do I update a submodule to a newer version?

Go into the submodule directory, fetch and check out the commit/branch you want (git fetch, git checkout main, git pull), then go back to the parent repo where the pointer now appears modified, and git add + git commit it. That commit records the new pointer so everyone else gets the same updated version. Crucially, also push the submodule's repo if you authored new commits there.

What's the difference between submodules and a monorepo?

Submodules keep each project in its own repository and link them by commit pointers — good when components have separate lifecycles, owners, or release schedules. A monorepo puts multiple projects in a single repository, so tightly-coupled code is versioned and changed together in one atomic commit. Choose a monorepo when cross-cutting changes are common; choose submodules when projects are genuinely independent.

What are the main downsides of submodules?

They add ceremony and footguns: it's easy to forget to commit the moved pointer or to push the submodule's own commits, leaving teammates stuck on an old version. Branch switching, merges, and CI all need submodule-aware commands (--recurse-submodules). Because of this friction, many teams prefer a monorepo or a package manager unless they specifically need independent repositories.