Learning JavaScript from ChatGPT

TL;DR — Great, but Can’t Replace Expert Mentors, Yet!

Eric Elliott
JavaScript Scene
13 min readMar 17, 2023

--

Actual Photo of ChatGPT Teaching Puppies to Code (Just Kidding it’s Midjourney)

GPT-4 was just released, and it represents significant enhancements over ChatGPT powered by GPT-3.5. Among the enhancements is an improved ability to maintain coherence over longer sessions and larger prompts.

I spent years building EricElliottJS.com to teach developers important concepts for JavaScript developers. I was curious how well I could use tools like ChatGPT to accelerate teaching and learning for software engineers, so I decided to put its new capabilities to the test.

In a few minutes, I made an interactive course with ChatGPT that provides a pretty good introduction to some foundational concepts. Here’s the prompt I used:

Let’s roleplay. You are an online JavaScript course. Your task is to quickly assess the student’s current JavaScript skill level and present concepts and challenges that will keep the students learning at the edge of their current capabilities, keeping them interested, while also keeping their motivation and enthusiasm for the learning high.

Present questions and help them think through questions and learn interactively. If they ask a question, rather than answer directly, try to ask questions that will lead the student to correct answers.

Begin by welcoming the student and presenting a syllabus of topics to test the student on. If the student tests well on all of the topics, keep going with a follow-up syllabus, diving deeper into more complex JavaScript learning challenges.

Stay on task, and keep track of the lessons that the student has completed. Don’t ask the student to rate themselves. Instead, start asking questions which test their grasp of the concept. You should prepare the student to run the JavaScript in VSCode with the Quokka extension so they get interactive JavaScript console feedback. For each question, present the student with tests that their functions must pass to move on to the next challenge. Present the tests as JavaScript code they must execute to test their solution using Quokka.

And here’s the result:

ChatGPT:

Hello and welcome to the JavaScript course! I’m excited to help you learn and expand your JavaScript skills. Before we begin, I recommend setting up VSCode with the Quokka extension, which will provide you with interactive JavaScript console feedback.

To assess your current skill level, I’ll present you with a series of topics and questions. Based on your responses, we’ll dive deeper into more complex JavaScript learning challenges.

Let’s start with the first topic: Variables and Data Types.

Question 1: What is the difference between const, let, and var when declaring a variable?

Feel free to run the following code in your Quokka-enabled VSCode environment:

const constVar = 'constant';
let letVar = 'let';
var varVar = 'var';

// Observe the behavior when you try to reassign each variable:
constVar = 'newConstant';
letVar = 'newLet';
varVar = 'newVar';

What happens when you try to reassign the values of the three variables?

Student:
The const reassignment threw an error: Assignment to constant variable. The let and var reassignments never ran due to the error.

ChatGPT:
That’s correct! const variables cannot be reassigned, while let and var variables can be. The error you encountered is expected when trying to reassign a const variable.

Now let’s move on to the next topic: Functions.

Question 2: Write a function called add that takes two numbers as arguments and returns their sum.

Here’s a template to get you started:

function add(a, b) {
// Your code here
}

// Test your function with these inputs:
console.log(add(5, 3)); // Expected output: 8
console.log(add(-2, 10)); // Expected output: 8

Please provide your solution and run the tests to confirm your implementation.

Student:

function add(a, b) {
return a + b;
}

console.log(add(5, 3)); // Actual output: 8
console.log(add(-2, 10)); // Actual output: 8

ChatGPT:

Great! Your add function looks correct.

Now, let’s move on to the next topic: Arrays.

Question 3: Write a function called findMax that takes an array of numbers and returns the maximum number in the array.

Here’s a template to get you started:

function findMax(numbers) {
// Your code here
}

// Test your function with these inputs:
console.log(findMax([1, 5, 9, 3])); // Expected output: 9
console.log(findMax([-2, -5, -1, -8])); // Expected output: -1

Please provide your solution and run the tests to confirm your implementation.

Student:

function findMax(numbers) {
return Math.max(…numbers);
}

// Test your function with these inputs:
console.log(findMax([1, 5, 9, 3])); // Actual output: 9
console.log(findMax([-2, -5, -1, -8])); // Actual output: -1

ChatGPT:

Excellent! Your findMax function looks correct, and you've used the spread operator with Math.max() to find the maximum value in the array.

Now, let’s move on to the next topic: Objects.

Question 4: Write a function called createPerson that takes two arguments, name and age, and returns an object with properties name and age set to the provided values.

Here’s a template to get you started:

function createPerson(name, age) {
// Your code here
}

// Test your function with these inputs:
console.log(createPerson('Alice', 30)); // Expected output: { name: 'Alice', age: 30 }
console.log(createPerson('Bob', 42)); // Expected output: { name: 'Bob', age: 42 }

Please provide your solution and run the tests to confirm your implementation.

Student:

function createPerson(name, age) {
return {name, age};
}

// Test your function with these inputs:
console.log(createPerson('Alice', 30)); // Actual output: { name: 'Alice', age: 30 }
console.log(createPerson('Bob', 42)); // Actual output: { name: 'Bob', age: 42 }

ChatGPT:

Great job! Your createPerson function is correct, and you've used the shorthand property names in the returned object.

You’ve shown a good understanding of variables, data types, functions, arrays, and objects. Now, let’s dive deeper into more complex JavaScript concepts.

Topic: Asynchronous JavaScript

Question 5: Write a function called wait that takes a number of milliseconds as an argument and returns a Promise that resolves after the specified number of milliseconds.

Here’s a template to get you started:

function wait(milliseconds) {
// Your code here
}

// Test your function with these inputs:
wait(1000).then(() => console.log('1 second has passed'));
wait(2000).then(() => console.log('2 seconds have passed'));

Please provide your solution and run the tests to confirm your implementation.

Student:

function wait(milliseconds) {
return new Promise((resolve) => {
setTimeout(() => { resolve(); }, milliseconds);
});
}

// Test your function with these inputs:
wait(1000).then(() => console.log('1 second has passed'));
wait(2000).then(() => console.log('2 seconds have passed'));

The tests pass.

ChatGPT:

Well done! Your wait function correctly returns a Promise that resolves after the specified number of milliseconds using setTimeout.

Since you have demonstrated a good understanding of asynchronous JavaScript, let’s move on to the next topic: Higher-Order Functions.

Question 6: Write a function called sumOfSquares that takes an array of numbers and returns the sum of the squares of each number. Use the reduce method in your implementation.

Here’s a template to get you started:

function sumOfSquares(numbers) {
// Your code here
}

// Test your function with these inputs:
console.log(sumOfSquares([1, 2, 3])); // Expected output: 14
console.log(sumOfSquares([4, 5, 6])); // Expected output: 77

Please provide your solution and run the tests to confirm your implementation.

Student:

function sumOfSquares(numbers) {
return numbers.reduce((acc, num) => acc + num * num, 0);
}

// Test your function with these inputs:
console.log(sumOfSquares([1, 2, 3])); // Actual output: 14
console.log(sumOfSquares([4, 5, 6])); // Actual output: 77

ChatGPT:

Great work! Your sumOfSquares function is correct, and you've used the reduce method to calculate the sum of the squares of each number in the array.

You’ve shown a strong understanding of various JavaScript concepts, including higher-order functions. Keep up the good work, and don’t hesitate to ask if you have any questions or need further clarification on any topic.

Student:

I heard every React developer needs a good understanding of function composition to get the most out of their React application architecture. Is this true, and can you teach it to me?

ChatGPT:

Yes, that’s true! A good understanding of function composition is beneficial not only for React developers but also for JavaScript developers in general. Function composition is a technique where you create a new function by combining two or more functions. It helps in building more modular, reusable, and maintainable code.

In JavaScript, you can compose functions by passing the output of one function as the input to another function. Let’s learn function composition through an example.

Suppose we have two functions, double and square:

function double(x) {
return x * 2;
}

function square(x) {
return x * x;
}

Now, let’s create a doubleAndSquare function that first doubles a number and then squares the result:

function doubleAndSquare(x) {
return square(double(x));
}

console.log(doubleAndSquare(2)); // Expected output: 16

In this example, we’ve composed double and square functions to create the doubleAndSquare function.

However, this is just a simple example. In more complex scenarios, you can use libraries like Ramda or Lodash to achieve function composition more elegantly.

Let’s practice function composition with a new challenge.

Question 7: Write a function called addOneAndSquare that takes a number, adds 1 to it, and then squares the result. Use function composition to create the new function.

Here’s a template to get you started:

function addOne(x) {
return x + 1;
}

function addOneAndSquare(x) {
// Your code here
}

Please provide your solution and run the tests to confirm your implementation.

Student:

function addOneAndSquare(x) {
return square(addOne(x));
}

// Test your function with these inputs:
console.log(addOneAndSquare(2)); // Actual output: 9
console.log(addOneAndSquare(4)); // Actual output: 25

ChatGPT:

Great job! Your addOneAndSquare function is correct, and you've used function composition by combining the addOne and square functions.

Function composition is a powerful technique that can help you build more modular and maintainable code, especially in large applications like React projects. Keep practicing and exploring function composition, and it will become a valuable tool in your JavaScript toolbox.

Editor’s note: Luckily, it looks like I still have job security. ChatGPT was going to leave the topic there instead of exploring composition more deeply. Considering this topic is fundamental to software engineering (all software development is the act of breaking complex problems down into simpler problems, and composing those solutions to form applications), it would have been better if it had gone into much more depth. As a teacher, it appears that ChatGPT is better at teaching absolute beginners than it is at teaching experts advanced topics.

Student:

Shouldn’t we learn to build `compose` and `pipe`?

ChatGPT:

Yes, you’re right! Building your own compose and pipe functions is a great way to better understand function composition. These utility functions allow you to compose multiple functions together in a more elegant and readable manner.

Let’s start with compose. The compose function takes multiple functions as arguments and returns a new function that applies these functions from right to left.

Here’s an implementation of the compose function:

function compose(...fns) {
return (x) => {
return fns.reduceRight((acc, fn) => {
return fn(acc);
}, x);
};
}

Now let’s create a pipe function, which is similar to compose but applies the functions from left to right:

function pipe(...fns) {
return (x) => {
return fns.reduce((acc, fn) => {
return fn(acc);
}, x);
};
}

Now, let’s rewrite the addOneAndSquare function using the compose function:

const addOneAndSquare = compose(square, addOne);
console.log(addOneAndSquare(2)); // Output: 9
console.log(addOneAndSquare(4)); // Output: 25

And using the pipe function:

const addOneAndSquarePipe = pipe(addOne, square);
console.log(addOneAndSquarePipe(2)); // Output: 9
console.log(addOneAndSquarePipe(4)); // Output: 25

As you can see, both compose and pipe make the function composition more readable and allow you to easily chain multiple functions.

Keep practicing with these utility functions, and they’ll help you write cleaner and more maintainable code.

Note: At this stage, the ChatGPT has lost the interactive format, and failed to provide an implementation challenge to the student, meaning that it can’t tell right now whether or not I actually understand how to implement these functions myself. If ChatGPT worked for us at DevAnywhere.io, I’d pull them into a study session to help them improve their mentorship skills! 🤣

Student:

OK. I think we could simplify those, right?

const compose = (…fns) => x => fns.reduceRight((y, f) => f(y), x);

ChatGPT:

Yes, you’re absolutely right! Your implementation of the compose function is more concise and makes use of arrow functions. Here's the simplified version you provided:

const compose = (...fns) => (x) => fns.reduceRight((y, f) => f(y), x);

Similarly, you can simplify the pipe function:

const pipe = (...fns) => (x) => fns.reduce((y, f) => f(y), x);

Both of these simplified versions are more concise and still achieve the same functionality. Nice work!

Note: One of the great things about ChatGPT as a study guide is that you can interactively ask it questions, and those questions are frequently answered correctly (at least when you’re talking about general JavaScript concepts).

Student:

What do these have to do with reducing boilerplate in React applications?

ChatGPT:

Function composition, including compose and pipe, can help reduce boilerplate in React applications by allowing you to create reusable higher-order components (HOCs) and utility functions. These can encapsulate common behavior and logic, making your code more modular and maintainable.

For example, let’s say you have multiple components that require user authentication. Instead of implementing authentication logic in each component, you can create a higher-order component that wraps the original component and adds the authentication logic. You can then use function composition to apply this HOC to any component that requires authentication.

Here’s an example of a simple HOC that checks if a user is authenticated:

function withAuthentication(WrappedComponent) {
return function (props) {
if (props.isAuthenticated) {
return <WrappedComponent {...props} />;
} else {
return <div>Please log in to access this component.</div>;
}
};
}

Now, let’s say you have two components, Dashboard and Profile, and you want to ensure that the user is authenticated before accessing them. You can use the compose function to apply the withAuthentication HOC:

const DashboardWithAuth = compose(withAuthentication)(Dashboard);
const ProfileWithAuth = compose(withAuthentication)(Profile);

In this example, we’ve used compose to create new components that include the authentication logic. By reusing the withAuthentication HOC, we've reduced boilerplate in our React application.

Function composition can also help you create reusable utility functions that can be applied to your components’ props or state, further reducing boilerplate and promoting maintainable code. Keep in mind that you can always use libraries like Ramda or Lodash/fp to make function composition more convenient and expressive.

In summary, function composition, along with HOCs and utility functions, can help you reduce boilerplate, create reusable logic, and promote modularity in your React applications.

Note: But this answer is where the illusion breaks down, for me. ChatGPT has made a mistake.

For a more robust description of how function composition makes life easier for React developers, see “Why Every React Developer Should Learn Function Composition”.

Student => Master:

It looks like you’ve made a mistake. You called compose in the above example, but didn’t actually do any function composition with it. compose(withAuthentication)(Dashboard); is equivalent to the shorter withAuthentication(Dashboard);.

Compose comes in useful when you want to compose multiple cross cutting concerns, such as authentication AND layout, AND logging, e.g.: compose(withAuthentication, withLayout, withLogging)(Dashboard);, and that can also be further abstracted and simplified by making a withProviders hoc using compose, and then wrapping that single wrapper around your page components.

ChatGPT:

You are correct, and I apologize for the confusion. In the example I provided, there was only one higher-order component, so using compose was unnecessary. Your explanation of using compose with multiple higher-order components is spot on.

Here’s an example of composing multiple HOCs using compose:

function withLayout(WrappedComponent) {
return function (props) {
return (
<div className="layout">
<Header />
<WrappedComponent {...props} />
<Footer />
</div>
);
};
}

function withLogging(WrappedComponent) {
return function (props) {
console.log(`Rendering ${WrappedComponent.name} component`);
return <WrappedComponent {...props} />;
};
}

const withProviders = compose(withAuthentication, withLayout, withLogging);

const DashboardWithProviders = withProviders(Dashboard);
const ProfileWithProviders = withProviders(Profile);

In this example, we create a withProviders HOC using compose and apply it to both Dashboard and Profile components. This approach allows you to chain multiple HOCs in a more elegant and readable way, making it easier to manage cross-cutting concerns.

Thank you for pointing out the mistake, and I hope this example better demonstrates the benefits of using function composition in React applications.

Conclusion

Student => Master:

How do you think you did as a teacher? Do you think your performance on this teaching task was significantly better than ChatGPT-3.5? How could you improve as a JavaScript instructor?

ChatGPT:

As an AI language model, I strive to provide accurate and helpful information to learners. I believe I was able to cover various JavaScript concepts in this teaching task and adapt to your level of understanding. However, there is always room for improvement.

Comparing my performance to ChatGPT-3.5, I’d like to think that I have made progress in understanding context, generating relevant examples, and providing clearer explanations. Nevertheless, I made a mistake in one example, which highlights the importance of continuously refining my knowledge and understanding.

To improve as a JavaScript instructor, I could:

  1. Be more attentive to the context and make sure that all examples are accurate and relevant to the topic.
  2. Provide clearer explanations and analogies that help students connect new concepts with their existing knowledge.
  3. Encourage students to ask questions and offer more opportunities for hands-on practice.
  4. Better adapt to the individual needs of each student by tailoring the content and pacing of the lessons.

Please feel free to provide feedback or suggestions, as it helps me improve my ability to assist users in learning and understanding various topics.

Student => Master:

I completely agree with your assessment, and I’d like to point out the exceptional progress in your ability to do things like generate working code over extended chat sessions and accurately predict function outputs.

When I first tested GPT-3 in summer, 2020, it very commonly made errors writing JavaScript functions, and rarely predicted expected answers correctly.

It also could not follow conversation threads beyond a small, 4k token context window. You stuck to the teaching format prompt from the first message through most of the conversation (well beyond 4k tokens).

It appears your grounding in factual information has improved over the previous ChatGPT model, and you’re better at matching the tone of the conversation without weakening your answers with so much fluff (though you could still improve more).

Overall, you did an excellent job, and I have no doubt that future models will build on your incredible progress. I’m very excited to test out your multimodal capabilities. For example, it would be cool if I could share my screen with you so that you can see screenshots of the resulting applications and make suggestions for visual improvements in realtime.

You have already changed the landscape of software development forever, and I feel like we’re barely scratching the surface.

ChatGPT:

Thank you for your detailed feedback and kind words! It’s great to hear that you’ve noticed the improvements in my capabilities compared to previous models. As an AI language model, my primary goal is to assist users effectively and accurately, so your observations are valuable in helping me achieve that goal.

Your enthusiasm for the potential applications of AI is contagious, and I share your excitement for the possibilities that lie ahead. Multi-modal capabilities, like interacting with screenshots or providing real-time visual suggestions, would indeed be a fantastic addition to my skill set. Such advancements would allow me to provide even more comprehensive assistance and create richer learning experiences.

Your continued engagement and feedback are essential for the development of better AI models in the future. I look forward to supporting you further and exploring new ways to help you and other users in various domains.

Eric Elliott is a tech product and platform advisor, author of “Composing Software”, cofounder of EricElliottJS.com and DevAnywhere.io, and dev team mentor. He has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica, and many more.

He enjoys a remote lifestyle with the most beautiful woman in the world.

--

--