LLD: Design a low level / Machine coding of a package manager (yarn)
Designed a Yarn-like package manager that deterministically resolves and installs package dependencies using a lock file, clean separation of concerns, and DFS-based dependency resolution.
If you’re looking for 1:1 mentorship with a strong focus on LLD (core emphasis on Multithreading), HLD, DSA, and system design research papers, feel free to reach out.
📩 Contact: programmingappliedai@gmail.com
Core Features
Install a package
Handle dependencies
Maintain a lock file (like
yarn.lock)Avoid reinstalling already installed packages
Resolve dependency graph
Support versioning (basic)
❌ Out of scope (intentionally):
Real HTTP registry
SemVer ranges (
^,~)Parallel downloads
Native builds
High-Level Design (HLD)
+------------------+ | PackageManager | +------------------+ | v +------------------+ +----------------+ | DependencySolver | ---> | PackageRegistry| +------------------+ +----------------+ | v +------------------+ | Installer | +------------------+ | v +------------------+ | LockFileManager | +------------------+import java.util.*; // ----------- PACKAGE ENTITY ------------ class Package { String name; String version; Map<String, String> dependencies; Package(String name, String version, Map<String, String> dependencies) { this.name = name; this.version = version; this.dependencies = dependencies; } } // ----------- PACKAGE REGISTRY ------------ interface PackageRegistry { Package getPackage(String name, String version); } // Mock npm registry class InMemoryPackageRegistry implements PackageRegistry { private final Map<String, Package> store = new HashMap<>(); public InMemoryPackageRegistry() { store.put("react@18.0.0", new Package("react", "18.0.0", Map.of("scheduler", "1.0.0"))); store.put("scheduler@1.0.0", new Package("scheduler", "1.0.0", Map.of())); store.put("lodash@4.17.0", new Package("lodash", "4.17.0", Map.of())); } @Override public Package getPackage(String name, String version) { String key = name + "@" + version; if (!store.containsKey(key)) { throw new RuntimeException("Package not found: " + key); } return store.get(key); } } // ----------- LOCK FILE MANAGER ------------ class LockFileManager { private final Map<String, String> lockFile = new HashMap<>(); public void lock(String name, String version) { lockFile.put(name, version); } public boolean isLocked(String name) { return lockFile.containsKey(name); } public String getLockedVersion(String name) { return lockFile.get(name); } public void printLockFile() { System.out.println("\n📦 yarn.lock"); lockFile.forEach((k, v) -> System.out.println(k + " -> " + v)); } } // ----------- DEPENDENCY SOLVER ------------ class DependencySolver { private final PackageRegistry registry; private final LockFileManager lockFile; private final Set<String> visited = new HashSet<>(); DependencySolver(PackageRegistry registry, LockFileManager lockFile) { this.registry = registry; this.lockFile = lockFile; } public void resolve(String name, String version) { String key = name + "@" + version; if (visited.contains(key)) return; visited.add(key); Package pkg = registry.getPackage(name, version); for (Map.Entry<String, String> dep : pkg.dependencies.entrySet()) { resolve(dep.getKey(), dep.getValue()); } lockFile.lock(name, version); } } // ----------- INSTALLER ------------ class Installer { private final Set<String> installed = new HashSet<>(); public void install(String name, String version) { String key = name + "@" + version; if (installed.contains(key)) return; System.out.println("⬇ Installing " + key); installed.add(key); } } // ----------- PACKAGE MANAGER (YARN) ------------ class YarnPackageManager { private final PackageRegistry registry = new InMemoryPackageRegistry(); private final LockFileManager lockFile = new LockFileManager(); private final DependencySolver solver = new DependencySolver(registry, lockFile); private final Installer installer = new Installer(); public void install(String name, String version) { solver.resolve(name, version); // install in locked order lockFile.printLockFile(); lockFile.lockFile.forEach(installer::install); } } // ----------- MAIN ------------ public class Main { public static void main(String[] args) { YarnPackageManager yarn = new YarnPackageManager(); yarn.install("react", "18.0.0"); } }Sample Output
📦 yarn.lock scheduler -> 1.0.0 react -> 18.0.0 ⬇ Installing scheduler@1.0.0 ⬇ Installing react@18.0.0Explanation about different classes used
1️⃣
PackageResponsibility:
Represents a single package artifact from the registry.Why it exists:
Encapsulates package metadata and avoids passing raw maps around.Functionality:
Stores package name
Stores version
Stores dependencies (
dependencyName → version)
Key Methods:
getName()→ returns package namegetVersion()→ returns versiongetDependencies()→ returns dependency list
📌 This is a pure data object (Entity / Model).
2️⃣
PackageRegistry(Interface)Responsibility:
Abstracts the source from where packages are fetched.Why it exists:
Allows us to swap registry implementations without touching business logic.Functionality:
Defines contract to fetch a package by name & version
Key Method:
fetchPackage(name, version)
📌 Interview keyword: “Abstraction for extensibility.”
3️⃣
InMemoryPackageRegistryResponsibility:
Simulates an npm/yarn registry.Why it exists:
Provides a controllable data source for machine coding and testing.Functionality:
Maintains a private in-memory map of packages
Returns package metadata on demand
Throws error if package does not exist
Key Method:
fetchPackage(...)→ retrieves package from store
📌 Can later be replaced by HTTP / file-based registry.
4️⃣
LockFileManagerResponsibility:
Maintains deterministic dependency versions (yarn.lock).Why it exists:
Ensures same versions are installed every time.Functionality:
Stores resolved package versions
Preserves install order
Exposes read-only access to locked packages
Prints lock file
Key Methods:
lockPackage(name, version)getLockedPackages()→ immutable viewprintLockFile()
📌 Guarantees reproducible builds.
5️⃣
DependencyResolverResponsibility:
Resolves the full dependency graph.Why it exists:
Separates dependency resolution from installation logic.Functionality:
Performs DFS traversal on dependency graph
Ensures dependencies are resolved before parent
Avoids duplicate resolution using
visitedWrites resolved versions to lock file
Key Method:
resolveDependencies(name, version)
📌 Time complexity: O(N + E) where N = packages, E = dependencies.
6️⃣
InstallerResponsibility:
Handles actual installation of packages.Why it exists:
Keeps installation logic isolated and reusable.Functionality:
Tracks already installed packages
Installs each package exactly once
Simulates writing to
node_modules
Key Method:
install(name, version)
📌 Can later support parallel installs, retries, caching.
7️⃣
YarnPackageManagerResponsibility:
Orchestrator / Facade for the entire system.Why it exists:
Provides a clean API similar to realyarn install.Functionality:
Triggers dependency resolution
Generates lock file
Installs packages in correct order
Key Method:
install(packageName, version)
📌 Acts as the single entry point for clients.
8️⃣
MainResponsibility:
Program entry point.Functionality:
Creates
YarnPackageManagerInitiates package installation
📌 Used only for execution/testing.
🧠 One-Line Design Summary (Interview Gold)
“The system cleanly separates dependency resolution, installation, and registry access while using a lock file to ensure deterministic package installs.”
If you want, next I can:
Add cycle detection
Add version conflict resolution
Convert this into a proper machine-coding question with test cases
Add UML / class diagram
Just say 👍
Want Next?It can extend to:
Version conflict resolution (
react@17vs18)Cycle detection (
A → B → A)Parallel install simulation
node_modulestree structureFull machine-coding problem statement + tests

