Red-Green-Code

Deliberate practice techniques for software developers

  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter

LeetCode Tip 20: Learning Language Syntax

By Duncan Smith Leave a Comment May 24 0

LeetCode 2023

Succeeding at algorithmic coding interviews requires learning about algorithms and data structures. But it’s not enough just to know the theory of algorithms. You also have to implement them quickly in a real (non-pseudocode) programming language. If you’re writing code on a whiteboard or a plain text editor, your code has to be good enough to impress the interviewer, but you can take a few shortcuts with syntax. If you’re using an online judge like LeetCode that compiles and runs your code against test cases, the syntactical standards are higher.

Learning a programming language to solve LeetCode problems is both easier and harder than learning a programming language to solve real-world coding problems. It’s easier because you don’t have to learn everything about the language and its associated infrastructure. For a real-world problem, you might have to learn a front-end language, a back-end language, libraries and frameworks, a database, pseudo-languages like HTML and CSS, and other details that go into creating a working software solution. For a LeetCode problem, you only need to learn a small subset of a programming language and its libraries. And you don’t have to set up a programming environment or automated tests. You just type code into a box and click a button to run it. Everything else is handled for you.

On the other hand, many programmers find interviews to be more challenging than their daily work. Even if we ignore the challenge of learning algorithms and data structures (see the previous tips for more on that) there is still the challenge of coding on demand under both time pressure and the watchful eye of the interviewer. To do well in these conditions, you need to work on language fluency.

The way to improve your programming language fluency is to practice the syntax of a language the same way you practice implementing algorithms. If you’re working on a problem that asks you to solve a 2D maze and you have trouble representing the maze as an array, study how your language handles arrays. Isolate that component of the problem, separating it from the maze-solving algorithm, and practice until it becomes second nature. Make sure you can address the cell in a particular row and column, move North or West or Northwest in the maze, and check whether you are out of bounds. Don’t return to the maze algorithm until you’re clear on the array syntax.

The goal of practicing language syntax is to reach the point where you can express concepts in code as easily as you can write pseudocode, math equations, or diagrams. You never want to know the solution to the problem but be unable to write the correct keywords and tokens to implement it. Ideally, if you can think of the pseudocode for a solution, your fingers should be able to type the equivalent code without much intervention from your brain.

Besides language syntax, you also need to practice your language’s standard library. We’ll cover that in the next tip.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 19: How To Use a Problem List

By Duncan Smith Leave a Comment May 17 0

LeetCode 2023

To use LeetCode effectively, you have to find and practice model problems. A good model problem is one that you haven’t solved too many times, is the right difficulty for your level, and focuses on a core concept rather than extraneous details.

Once you find a problem that meets these criteria, you can learn a lot by repeatedly solving it using a spaced repetition process. But even a great problem doesn’t stay useful forever. It eventually becomes so easy that you get nothing out of solving it again. Or after a few repetitions, you might find that it still doesn’t make sense to you because it requires a concept that you haven’t yet learned. In either case, you need a list of problems that you can draw from when you need a new one to practice.

You don’t have to look far to find a problem list. Since the goal is to master a set of concepts, it’s fine just to use the LeetCode problem page, where each problem is tagged with a concept. But with new problems being added every week, the number of problems in the official LeetCode list can seem overwhelming. The solution: use a problem list where someone has filtered the full LeetCode list down to a manageable size. Two examples are Tech Interview Handbook, the source of the well-known Blind 75 list, and NeetCode, which includes Blind 75 and other lists.

Even if you use a curated list, it’s still good to filter it according to your own unique criteria. Unless you have an expert tutor choosing a problem specifically for you, a list can only give you problems that are good for the average LeetCode user. A problem may still not meet your needs at a specific time on your practice journey. So, as you’re making your way through a list of problems, don’t worry about skipping a problem if it isn’t what you need.

Problem lists have different options for which problems to solve and in what order. But one option most of them provide is topic-wise practice. If you have a flexible schedule, this tried-and-true approach works best, along with spaced repetition. It’s the most straightforward way to learn each concept.

To use topic-wise practice, first decide on a concept to practice — for example, binary search. Then pick a problem list and find the recommended set of problems for that concept. Look through the list and find a problem to start with. You can skip problems if you already have experience with the concept, but when in doubt, start with the first problem. To practice it, use spaced repetition intervals: Start by practicing it every day, then every 2 days, 4 days, and so on. You can skip interval lengths if the problem is too easy. Once you get past the 1 day interval, you’ll need something else to practice. So pick the next problem on the list.

As you build out a spaced repetition schedule, you’ll generate a set of problems that cover a single concept and are spread out in some spaced distribution, with easier problems using shorter intervals and harder problems using longer ones. Once you get to the Medium problems for a concept, you should have a good idea about the fundamentals of the concept, and you should be familiar with a few problems that cover the key insights for that concept.

As you get started on your second concept, you’ll adjust your spaced repetition plan. Rather than a set of problems covering one concept, you’ll mix in problems that cover different concepts. This is by design, since you want to continue repeating the original concept so you don’t forget it while you learn the new concept. Let’s say your second concept is hash tables. For some repetitions, you’ll solve problems from binary search, and for others you’ll use hash table problems. So you’ll be learning hash tables while keeping binary search fresh by periodically solving those problems. By continuing this process, you can make your way through the full list of concepts.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 18: Learn General Skills From Specific Problems

By Duncan Smith Leave a Comment May 10 0

LeetCode 2023

Using a practice journal for your LeetCode practice helps you learn as much as you can from a single problem. This requires a different mindset than using a problem list from Tech Interview Handbook, NeetCode, or the Daily LeetCoding Challenge. When you use a problem list, your goal is to get a variety of experience from a set of problems. A practice journal, on the other hand, helps you learn one problem as thoroughly as possible.

You can learn a lot by working on one problem through multiple repetitions. Every LeetCode problem covers one or more common concepts. By repeatedly solving the same problem, you can focus on the concept rather than having to figure out a new scenario, as you would if you kept picking new problems. For example, there’s a binary search problem that asks you to add events to a calendar. The first time you solve that problem, you need to spend time understanding the scenario. But the scenarios in LeetCode problems are less complex than the associated algorithm. So it may take only one or two repetitions to master the scenario, at which point you can focus on the algorithm.

Although the scenario may be simple, it is still a useful part of the problem. Recalling the steps of an algorithm is difficult in the abstract. Studying algorithms with an associated a scenario gives your brain something to latch onto. When you encounter a similar problem, rather than having to recall “binary search,” you can instead recall “how to add an event to a calendar without double booking.” As you practice more model solutions for an algorithm, you’ll learn multiple scenarios that help you approach the algorithm from different angles.

Practicing the same binary search problems multiple times doesn’t directly teach you to solve new binary search problems. But it’s a prerequisite for that skill. It helps you master binary search and several associated scenarios. When you encounter a new problem that tickles your binary search sense, you can focus on the specifics of that new problem, confident that you have mastered the implementation.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 17: What To Write in Your Practice Journal

By Duncan Smith Leave a Comment May 3 0

LeetCode 2023

A key part of effective LeetCode practice is keeping a practice journal. Although LeetCode automatically tracks your solution submissions and can show you your previously submitted code, a journal gives you a more customized record of your practice sessions. You can use this to focus on the areas you need to study most.

In its most basic form, a practice journal is a list of time-stamped entries at the bottom of your model solution document. Whenever you repeat a problem, add the current date and some information about your practice session. At a minimum, write one sentence explaining the result. For example, “Solved it easily” or “Solved most of it, but got stuck on a few edge cases” or “Off by one in the binary search.” Then, if you find yourself repeating the same comments the next time around, you know where to target your practice efforts. Even a small journal entry like this will help. But with a few additional minutes after each repetition, you can get even more out of your journal. Here are two categories of notes to include.

First, you can record spreadsheet-style data. It’s best if you use an actual spreadsheet for this, so you can write formulas to calculate some fields automatically. Some ideas for what to record:

  • Timestamp: When you practiced.

  • Practice time: How long you spent solving the problem. You should observe this time decreasing as you get better at a problem.

  • Days between repetitions: Calculate the number of days since the last repetition. Use this with your spaced repetition system to ensure that you’re practicing at the correct intervals.

  • Next practice interval (days): Decide what amount to put in this field based on how well you did for the current repetition. The more easily you solved the problem, the higher the number can be.

  • Days remaining until next interval: Calculate this field and use it to find a problem that’s ready to practice (pick the problem with the smallest number).

  • Accepted on first run (yes/no): Record whether you got the problem on the first try, or whether it took a few tries. For a canonical problem that you want to master, the goal is to learn it well enough that you don’t have to rely on the compiler or the unit tests to guide you in the right direction.

Besides the spreadsheet entries, free-form journal entries are also useful. The goal of this type of section is to self-evaluate how well you know a problem and which areas are giving you trouble. Include notes about any areas where you got stuck, even if you ended up solving the problem successfully. These notes can help you polish your model solution, turning it into a solution that is exactly customized for you. You can also find problem areas by looking for bugs that show up repeatedly in your journal entries. This is more efficient than going through previous code submissions.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 16: The Daily Practice Process

By Duncan Smith Leave a Comment Apr 26 0

LeetCode 2023

A daily practice habit can help you improve, but not every daily practice routine works equally well. If done incorrectly, it can become more of a chore to check off your to-do list than a useful learning tool. As we have seen so far this year, the LeetCode practice process relies on choosing model problems, writing model solutions, and practicing them using a spaced repetition process. Here are two more key ideas to ensure that your daily practice routine works smoothly.

First, problem selection. As you are learning the set of fundamental LeetCode concepts, the best approach for problem selection is what competitive programmers call topic-wise practice. Using this approach, you don’t worry about how to find the correct approach for an unknown problem. Instead, you pick problems where you know which algorithm or technique to use, and focus on learning how to implement it. To ensure that you get a well-rounded understanding, choose problems that use the algorithm or technique in different ways. To find relevant problems, use LeetCode tags or curated lists like the ones on Tech Interview Handbook.

Finding topic-wise problems is also a good way to take advantage of the daily challenge. As you’re working on the daily challenge, look for problems that use a familiar topic in a new way. When you find a problem like this, add it to your topic-wise list. By collecting a few problems for each topic, you can learn it more thoroughly than you would just by practicing one problem on a topic and moving on.

The second key idea: Keep a practice journal for each model problem. When you repeat a problem, record the date and your observations about that repetition. If you had trouble solving the problem (even if you eventually solved it) isolate the part of the problem that gave you trouble and make a note of it. As you record your daily progress, also review your model solution. If you can update the solution to make the challenging part of the problem clearer, this is the time to improve the code, the description, or both. If you check your notes for the previous repetition and find that you got stuck on the same part, that’s a sign that your model solution is still missing something. After multiple repetitions of this process, your understanding of the problem and your model solution should converge until you have mastered the problem and there’s nothing more to add to your solution.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 15: Daily LeetCode Practice

By Duncan Smith Leave a Comment Apr 19 0

LeetCode 2023

Doing the Daily LeetCoding Challenge gives you daily practice, exposure to new problems, and a community of fellow problem-solvers. But it’s possible to get caught up in the routine of solving each daily problem while missing critical aspects of daily practice. To see why, we’ll consider the goals of LeetCode practice.

As explained in that tip, it’s not enough just to practice a problem, or even multiple problems, every day. It’s better than nothing, but it’s inefficient and it increases the risk of getting stuck on a learning plateau. Instead, we need to focus on learning problem-solving concepts, as enumerated in the LeetCode tags. Each problem focuses on one or two concepts. Solving a problem helps to learn a concept, and learning the concept makes it easier to solve the problem.

Since the daily challenge problems are just regular LeetCode problems, they come with a list of tags. In recent months, the daily challenge organizers have also grouped problems into themes. So we’ll get a few days of stack problems, a few days of hash table problems, and so on. This is better than completely random problems, since solving multiple problems in a topic area helps learn that topic from multiple angles.

But the downside of the daily challenge approach is that everyone gets the same topic every day. If you have solved a lot of medium and hard dynamic programming problems, spending a few more days solving problems in that area may not be the best use of your time. And if you’re having trouble understanding how to use the union-find data structure to solve LeetCode problems, practicing it for a few days one week may not be enough time to make progress on learning that concept.

So you should think of the Daily LeetCoding Challenge as beginner mode practice. Though the problems may be hard, the practice process is rudimentary. To get the most out of daily practice, you’ll need to design a process that is more customized than the one that every daily challenge participant gets. In the next tip, we’ll see how to do that.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 14: The Daily LeetCoding Challenge

By Duncan Smith Leave a Comment Apr 12 0

LeetCode 2023

Since April 1, 2020, LeetCode has been running a Daily LeetCoding Challenge. Every day, they designate one problem as the daily challenge problem. If you solve that problem before midnight UTC, it extends your daily challenge streak and you get some LeetCoins added to your account. Other benefits include contests, badges, and a monthly Discord study group.

This gamified daily challenge is a good way to make sure you don’t forget about regular LeetCode practice. Once you get a streak going, it gives you an incentive to visit the site every day and solve the daily problem. On Discord, the study group lets you chat with people who are also solving daily problems.

The daily problem is also a convenient way to pick which problem to solve next. Sites like Tech Interview Handbook and NeetCode have more sophisticated problem roadmaps. But you can’t beat the simplicity of opening LeetCode and clicking on the fire icon to open the daily problem. In recent months, the problems have even followed a weekly theme, which helps you practice a particular algorithm or technique a few different ways.

If you’re serious about practicing LeetCode and collecting model problems and solutions, you should approach each daily challenge problem as a potential model problem. As you’re solving a problem, consider whether it would make a good model problem. Is the problem slightly more difficult than you’re comfortable with? Does it cover a topic that you want to know better? Does it focus on the fundamentals of that topic rather than on extraneous details? And finally, is it a good problem that is worth writing up and practicing repeatedly? If it meets these criteria, add it to your model problem queue to consider when you’re ready to start a new problem.

Despite the benefits of the daily challenge, there are things to watch out for when integrating it into your overall practice system. I’ll cover those in the next tip.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 13: Spaced Repetition Interval Lengths

By Duncan Smith Leave a Comment Apr 5 0

LeetCode 2023

When you use spaced repetition for LeetCode practice, keeping track of repetition intervals on your own is better than having practice software do it for you. Unlike vocabulary words, LeetCode model problems and solutions are complex, so you’ll only have time to practice a few of them per day. This gives you the time to consider the appropriate interval length and what your goals are for the current repetition. Here are some suggestions for what to focus on at each interval length.

0 days

It helps to practice a model solution even right after you finish reading it. If you stare at a solution for 10 minutes, then put it away and start typing it into LeetCode, you still probably won’t be able to recall the full solution from memory. You’ll have to use your understanding of the solution to come up with a working submission. As you try to reproduce the solution on day 0, you’ll notice which parts are harder than others. This will start the process of learning the solution, and it can help you find parts of the model solution that need to be updated with a clearer explanation.

1 day

The one-day interval works like this: You practice a solution on day 0, sleep for one night, and try to reproduce the solution the next day without looking at it again. This will expose different facets of the problem that you didn’t see the previous day. Sleeping is good for consolidating memories, but it also starts the process of forgetting. So at this interval, you may find that parts of the problem are difficult to remember. Recalling troublesome parts helps strengthen recall, and as on day 0, it can encourage you to make updates to the model solution description.

2-4 days

For the two-day interval, you practice a problem, skip one day without practicing it, and practice it the next day. For the four-day interval, you skip two days. The 2- and 4-day intervals increase the difficulty of remembering the details of a solution, but they are still short enough that you’ll remember most of the problem, including which algorithm you used and some parts of the code. But you may forget some implementation details if you haven’t practiced similar problems. This is a good time to think about how the algorithm works and how to apply it in more general cases.

8-16 days

At these intervals, you can forget major concepts required to solve the problem. So it’s important to slow down and avoid increasing the interval until you have mastered it at the current length. Since you’ll be practicing other problems in parallel, solutions to multiple problems can get mixed in your mind. You might recall a technique that worked on Problem A and mistakenly using it on Problem B. That provides an opportunity to learn how to map a problem with the solution that works for it, which is an important skill to have when you’re solving unknown problems.

32-64 days

With this amount of time between repetitions, you might completely forget a problem, almost as if you are seeing it for the first time. This shows that you are increasing the repetition interval too quickly. The 32- and 64-day intervals are a good time to make sure you have a solid understanding of every part of the solution, including how to pick the solution approach given the problem description, the high-level plan to implement the solution, any tricky details for particular steps, and finally all the low-level implementation details.

128-256 days

At very long intervals, the only options for solving a problem are 1) Retrieve the solution from your long-term memory, or 2) Figure out the solution from general experience. You won’t be able to rely on short-term memory from recent practice sessions. The goal is to use option 1. The purpose of model problem and solution practice is to learn canonical problems in such a way that you can implement them without thinking too much. For example, you should be able to implement the standard binary search algorithm without thinking about how to find the middle of an array, or considering the pros and cons of lo < hi vs. lo <= hi.

The 128- and 256-day intervals are not so much about learning a problem, and more about verifying that it’s really in your long-term memory. After some experience with the spaced repetition process, you should be able to work out every detail of a problem at the shorter intervals, and avoid lengthening the intervals too quickly. Then once you get to the longer intervals, you should always be able to solve the problem. If that works out according to plan, you can now archive the problem to make room for new ones.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 12: How To Use Spaced Repetition (Part 2)

By Duncan Smith Leave a Comment Mar 29 0

LeetCode 2023

As you use a spaced repetition process for LeetCode practice, there are more things to keep in mind than just reproducing the solution correctly. Here are more tips to get the most out of the process.

Make sure you’re practicing the right problem

Before you start spaced repetition practice, you need to choose a model problem and write a model solution. But it may take several repetitions to figure out whether the problem you selected is the best problem for your current level of mastery of a topic. If you write a detailed solution for a problem and refine it over several repetitions, but you still have trouble reproducing the solution, it may be best to leave that problem for later. Maybe the problem combines multiple techniques in a tricky way, or builds on fundamentals that you haven’t yet mastered. It’s fine to switch to another problem in the same area, and come back to the original problem later. Spaced repetition works well to strengthen your understanding of a topic, but it’s not designed for learning a topic from scratch.

Understand the solution rather than memorizing it

Another advantage of spaced repetition is that it tells you when you’re trying to memorize a solution rather than learning it. For short practice intervals (up to a few days), you won’t know for sure whether you really know a solution. Although you may succeed in reproducing it, this could be because you remember it from the previous interval. But as you increase the interval length, it becomes increasingly difficult to remember a solution unless you understand it (unless you’re using sophisticated memorization techniques, which are better for card tricks than programming). So once you get to a 15- or 30-day interval, you can be confident that you know how the solution works, not just how to reproduce the code.

Develop building blocks to use for other problems

Repeating a problem that you have already solved uses skills that differ from the ones required to solve an unknown problem. But the first type of practice can help with the second. For example, if you encounter an unknown problem that seems like it may need a binary search solution, that knowledge won’t help unless you can implement binary search. So as you write your model solutions, remember that you’ll need to use these solutions for other problems in the future. When possible, write them in a way that isn’t too specific to the problem at hand. Some competitive programmers use this approach by assembling a code notebook to use as a guide for implementing tricky algorithms. But interviewers generally don’t allow this type of notebook, since they want to see you write code yourself. So for interviews, it’s best to keep the notebook in your head.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 11: How To Use Spaced Repetition (Part 1)

By Duncan Smith Leave a Comment Mar 22 0

LeetCode 2023

Spaced repetition might seem like a memorization process. In its simplest form, you use spaced repetition by simply reading a prompt and responding with the correct answer. In the LeetCode context, the prompt is the LeetCode problem description, and the answer is an accepted solution. If you solve the same model problem multiple times, you get better at solving that problem and similar problems.

But LeetCode problems and solutions are longer and more complex than standard spaced repetition content, so it doesn’t work to approach them the same way. Here are a few things you can try to do as you use the process.

Improve your model solution

The goal when writing a model solution is to explain the best way to solve the problem, using your own experience and what you have learned from other people’s solutions. But no matter how careful you are to include every relevant detail, you will always find out more about the problem as you practice it. Taking a break from a problem to do other things (including solving other problems) gives your subconscious time to work on it. When you come back to it, you’ll notice additional details, and you’ll probably find that some parts are more difficult than you thought they were when you wrote the solution. Use this insight to improve your model solution. If you had trouble with part of the problem during a repetition, update the solution to explain that part more clearly.

Adjust the problem difficulty

Learning through problem-solving is most efficient when you practice problems that are just hard enough. It’s not useful if they’re so easy that you can solve them in your sleep. And if they’re so hard that you can’t make progress on them, you won’t learn anything either. But it’s not always possible to find problems at exactly the right difficulty level, unless you have an instructor to write problems specifically for you. Spaced repetition can help in this situation. A problem that you have studied previously is easier if you try it again soon after you have solved it, and harder if you wait longer. So you can adjust the difficulty of a problem by adjusting the repetition interval. If it’s too easy, wait longer to solve it again. If it’s too hard, reduce the interval or improve the solution to better explain the hard parts.

Narrow down a problem area

As you practice a problem using spaced repetition, you may encounter the following situation: After practicing a problem a few times at increasing intervals, you remember most of the solution but forget part of it. This is fine. It’s the spaced repetition process working, making you aware of a specific part of the problem that you haven’t completely learned. When this happens, you could reduce the practice interval, which is what the spaced repetition process says to do when you have trouble with a repetition. But if you have already practiced the problem several times at longer intervals, reducing the practice interval may not be the best option. Instead, keep the current interval, but write a detailed analysis of the difficult area in your practice notebook. The reason not to go back to a shorter interval is that while that will help you solve the problem successfully, it promotes a memorization approach. Once you reach an interval of a week or two, the goal is to rely on understanding the solution rather than remembering it. If you remember anything, it will be the general solution structure rather than lines of code. Once you get to this level of understanding of a problem, it’s best to keep using it, even if it means you have trouble solving it. If things still aren’t working out after a few tries with the same interval, it’s fine to reduce the duration. But try the other approach first.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

  • « Previous Page
  • 1
  • …
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • Next Page »

Stay in the Know

I'm trying out the latest learning techniques on software development concepts, and writing about what works best. Sound interesting? Subscribe to my free newsletter to keep up to date. Learn More
Unsubscribing is easy, and I'll keep your email address private.

Getting Started

Are you new here? Check out my review posts for a tour of the archives:

  • 2023 in Review: 50 LeetCode Tips
  • 2022 in Review: Content Bots
  • 2021 in Review: Thoughts on Solving Programming Puzzles
  • Lessons from the 2020 LeetCode Monthly Challenges
  • 2019 in Review
  • Competitive Programming Frequently Asked Questions: 2018 In Review
  • What I Learned Working On Time Tortoise in 2017
  • 2016 in Review
  • 2015 in Review
  • 2015 Summer Review

Archives

Recent Posts

  • Do Coding Bots Mean the End of Coding Interviews? December 31, 2024
  • Another Project for 2024 May 8, 2024
  • Dynamic Programming Wrap-Up May 1, 2024
  • LeetCode 91: Decode Ways April 24, 2024
  • LeetCode 70: Climbing Stairs April 17, 2024
  • LeetCode 221: Maximal Square April 10, 2024
  • Using Dynamic Programming for Maximum Product Subarray April 3, 2024
  • LeetCode 62: Unique Paths March 27, 2024
  • LeetCode 416: Partition Equal Subset Sum March 20, 2024
  • LeetCode 1143: Longest Common Subsequence March 13, 2024
Red-Green-Code
  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter
Copyright © 2025 Duncan Smith