๐Ÿฆ‘ SquidLeet: A Command-Line LeetCode Practice Tool

A deep dive into the low-level design of SquidLeet, a command-line tool for practicing LeetCode problems.

Python
GraphQL
Docker
Linux
Nicholas Adamou
ยท7 min read
๐Ÿ–ผ๏ธ
Image unavailable

SquidLeet is a command-line tool designed to enhance the experience of practicing LeetCode problems. This article explores its low-level design, highlighting the modular architecture, key components, and how they interact to create a seamless user experience.

Overview of the Architecture

SquidLeet's architecture is modular, with distinct layers for API interactions, input handling, practice management, and utility functions. Here's a high-level overview of its architecture:

Key Components and Their Roles

1. CommandParser

The CommandParser component processes CLI arguments and validates inputs. It determines the user's desired practice mode, problem difficulty, and other configuration options.

Input Arguments:

  • --practice-mode: Specifies the mode (e.g., daily, random, custom, study-plan).
  • --difficulties: Filters problems by difficulty (easy, medium, hard).
  • --problems: Accepts specific problem slugs for custom mode.
  • --plan-name: Selects a study plan for study-plan mode.

2. PracticeModeManager

This module determines the appropriate practice mode based on the parsed inputs and delegates the request to the corresponding handler.

3. PracticeHandler

PracticeHandler serves as the core logic for managing different practice modes. It interfaces with CachedLeetCodeAPI to fetch problems and passes problem details to the SolutionHandler for further action.

4. CachedLeetCodeAPI

The CachedLeetCodeAPI module acts as a caching layer to store fetched problems temporarily. It helps reduce redundant API calls and speeds up problem retrieval.

In order to provide universal access to the cache, I followed the singleton pattern. This ensures that all components share the same cache instance and avoid redundant cache reads and writes.

In Python this is achieved by creating a file called CacheHandler.py with the following content:

from api.CachedLeetCodeAPI import CachedLeetCodeAPI

cached_api = CachedLeetCodeAPI(cache_expiry=3600)

This file is then imported wherever the cache is needed, ensuring a single cache instance across the application.

from handlers.CacheHandler import cached_api

How did I design the cache?

The cache design for CachedLeetCodeAPI involves several key components and processes, which are outlined below:

  1. Cache Directory:
  • The cache is stored in a directory, which defaults to the system's temporary directory if not specified. This directory is named "cached_leetcode_api" and is created if it doesn't exist.
  1. Cache Key:
  • A unique cache key is generated for each API request. This key is derived from a unique identifier, such as the query or API parameters, by computing the MD5 hash of the identifier. This ensures that each request has a distinct cache entry.
  1. Cache Expiry:
  • The cache has an expiration time, set by default to 3600 seconds (1 hour). This means cached data older than this duration is considered stale and will be refreshed by making a new API call.
  1. Cache Read/Write Operations:
  • Reading from Cache: When a request is made, the system first checks if a valid cache entry exists. If the cache file is present and not expired, the data is read from the cache and returned.
  • Writing to Cache: If there is a cache miss or the cache is expired, the data fetched from the API is written to the cache for future use.
  1. Fetching with Cache:
  • The _fetch_with_cache method orchestrates the caching logic. It attempts to read from the cache first and, upon a cache miss, invokes the API function to fetch the data and then writes the data back to the cache.
  1. Cached API Methods:
  • The class provides cached versions of several API methods, such as fetch_problems, fetch_daily_challenge, fetch_problem, and get_study_plan. Each method uses a unique identifier to manage its cache entries.

Here is a visual representation of the caching process using PlantUML:

This diagram illustrates the interaction between the user, the CachedLeetCodeAPI, the LeetCodeAPI, and the cache directory, highlighting the decision-making process for using cached data versus fetching new data from the API.

5. LeetCodeAPI

The LeetCodeAPI module encapsulates all interactions with the LeetCode platform using GraphQL. It supports fetching problems, study plans, and submitting solutions.

Core Methods:

  • fetch_problems: Retrieves a list of problems based on difficulty and filters.
  • fetch_daily_challenge: Fetches the daily coding challenge.
  • fetch_problem: Retrieves detailed information about a specific problem.
  • submit_solution: Submits user solutions and handles responses.

6. SolutionHandler

SolutionHandler manages the solution workflow, including creating template files with the starter code provided by the LeetCodeAPI, opening them in the user's preferred editor, and setting up file watchers for automatic submission.

7. Utilities

SquidLeet includes several utility modules for logging, file handling, and timer management:

  • Logger: Customizable logging levels (DEBUG, INFO, WARN, ERROR).
  • File Handler: Manages file creation for solutions.
  • Timer: Tracks time limits for solving problems.
  • Editor Resolver: Determines the appropriate command to open files in the user's preferred editor.

Interaction Flow

Here is a step-by-step breakdown of how SquidLeet processes a --mode daily command:

  1. User Input: The user enters python3 main.py --mode daily.
  2. Command Parsing: CommandParser parses inputs and validates them.
  3. Session Initialization: SessionManager initializes the LeetCode session.
  4. Mode Handling: PracticeModeManager delegates the request to DailyChallengeMode.
  5. Problem Fetching: LeetCodeAPI fetches the daily challenge if it is not found in CachedLeetCodeAPI.
  6. Solution Workflow:
  • SolutionHandler creates a solution file with the starter code provided by the LeetCodeAPI.
  • Opens the file in the editor.
  • Sets up a file watcher to monitor changes.
  • Submits the solution upon file save.

Design Considerations

1. Modular Design

Each component is designed to perform a specific task, making the codebase maintainable and extensible. For instance, adding a new practice mode requires implementing a new subclass of PracticeHandler.

2. Asynchronous API Calls

The use of ThreadPoolExecutor in LeetCodeAPI allows fetching problems in parallel, improving performance for multi-difficulty queries.

3. Extensibility

New features like additional practice modes or support for more languages can be added without disrupting existing functionality.

4. Error Handling

Graceful error handling ensures that the user is informed of any issues, such as missing session tokens or invalid problem slugs.

Challenges and Future Enhancements

Challenges

  • Managing authentication securely.
  • Handling API rate limits and network failures.

Future Enhancements

  • Detailed Analytics: Track user performance and provide insights.
  • Enhanced Logging: Support structured logging for better debugging.

Conclusion

SquidLeet demonstrates the power of modular design and efficient API integration. By providing a command-line interface for practicing LeetCode problems, it eliminates distractions and optimizes the coding experience. With its extensible design, SquidLeet is well-positioned to evolve into an even more powerful tool for developers.

If you liked this project.

You will love these as well.