Teaching past the "symbology" of a language I've been recently helping someone with their beginners Python programming course at their university (though I don't think the specific language is particularly important). 

At times I'm frustrated, because I seem to be unable to push them to bridge, what seems to me to be, incredibly small leaps in logic. 

At one point there was a question that stated "using the solution to Q1, solve Q2, but do it for a range of numbers".  They were able to do Q1 on their own, they were able to independently understand ranges in python, and how to iterate over them, and create lists with ranges. What they couldn't seem to understand, is that basically copying and pasting the code from Q1 into the for loop would do exactly what they asked. 

I tried to present analogous scenarios to give hints, heck tie it to real life ("you know how to make an omelette, what do you need to do to make 10?").  They seemed to "get" these other things, but when it came to looking at code and trying to apply the same logic, they just straight up had no idea what to do. 

After a long time they were not able to grasp the problem, and I didn't feel comfortable literally just telling them the answer.  They eventually talked to another person who gave them a solution (not what they used) and that helped them finish their own independent solution that was 99% of the way there. After the class after the homework was due, they noted how they essentially "face palmed" after the professor explained the expected solution (basically copy paste Q1, as I described).

I think part of the issue is that they have yet to cover functions, which would make it a whole lot easier: abstract away the Q1 part of the question, and thus make it easier for them to make that leap.  On the other hand I fear that I'm too far removed from the point where I had started learning to relate to their learning experiences (I remember when I didn't even understand for loops, so at least I know I had some of the same issues when I was younger).

Is there anything I can do in these types of scenarios?
Knowing how to use a tool is different from knowing why/when to use a tool

At a basic level, there is a difference between having certain skills and learning to apply/orchestrate these skills to achieve something more complex.

Forrest Gump was a good runner, but had no knack for understanding American football and needed to be told to run when it was time for him to run. Forrest has a skill (running), but was notoriously incapable of seeing the bigger picture (how to win a football match). This is made painfully clear when Forrest does not stop running when he reaches the endzone, instead running into the stadium.

In order for students to orchestrate the separate skills they have (iteration, calculation, ...) they must first be comfortable with the skills themselves and know them like the back of their hand.

Right now, your students are Forrest Gump. You taught them to run. You have not yet taught them how to decide for themselves why/when/where they should run and why/when/where they shouldn't.

Breaking down the problem is a skill on its own

Additionally, understanding that a complex challenge can be solved using an orchestration of basic skills requires your students to learn how to break down the problem.


  They seemed to "get" these other things, but when it came to looking at code and trying to apply the same logic, they just straight up had no idea what to do.


Based on your description, I infer that you're expecting this to automatically happen, but that's not the case.


  After a long time they were not able to grasp the problem, and I didn't feel comfortable literally just telling them the answer.


As a teacher, if your students do not know the answer, it's counterproductive to withhold the answer from them. It's akin to the old "if you don't know what's wrong then I'm not going to tell you" joke. Clearly, the fact that they are not finding the solution means that they are currently unable to find the solution. Withholding the answer perpetuates the issue and is going to lead to dispirited students who no longer care to look for the answer when they don't know the answer yet.

Teach them to break down the problem without touching any code. Walk them through the problem description, and analyse the issue to see if you can break it down into smaller steps.

In theory, you should never tell them how to solve it. All you should do it break down the problem in smaller and smaller steps until your students realize that they already know how to solve these (sufficiently small) steps. Depending in your students' skill level, you may have to break things down repeatedly.

If they really aren't getting it, then it's time to consider if the students have have enough exposure to the rudimentary skills that you're asking them to orchestrate. 

If you break down a complex problem down to e.g. number addition and students still don't respond to it ("I know how to solve that part"), then you're either dealing with disinterested students or students who aren't comfortable with the basics yet.

Functions are essential constructs toward abstraction


  I think part of the issue is that they have yet to cover functions, which would make it a whole lot easier: abstract away the Q1 part of the question, and thus make it easier for them to make that leap.


Yes, this is probably the biggest factor in why they're not thinking in abstracted terms. However, it's not so much that they should already know functions. 

When you introduce functions with simple examples that do not yet warrant functions, then students are left wondering what the purpose of a function is other than "to put things elsewhere". Coincidentally, my wife is taking a beginner's development course and this is exactly the stage they are at.

Rather, this is the exact moment for you to introduce functions. Let them solve Q1 on their own, and then refactor Q1 to use a function. Tell them that it's the same solution but in a different form and do not elaborate on the purpose of functions yet; only explain how it works.

Then, when you let them tackle Q2, let them do it by themselves, without any suggested for loop. Wait for them to make the mistake of copy/paste repeating themselves. More importantly, let them make that mistake.

When they've made that mistake, present a (fictional) Q3 in which you ask for them to perform this calculation for one million numbers, and then look as they all sigh and moan about the challenge.
Alternatively, you could phrase Q3 as a change to the calculation logic, which will require them to change all instances of their copy/pasted code. Bot options lead to the same result: unhappy students.

Now, you've created the perfect audience that is incentivized to learn about method abstraction and how to prevent copy/pasting in code. Refer them back to your Q1-with-a-function solution. Work out Q2 using that pre-existing function. Work out Q3 with that same function.

This will convey the importance of abstraction (functions) as opposed to brute force (copy pasting). Work smarter, not harder.

A practical example


  Pythagorean theorem - calculate the hypotenuse of a right angle triangle when given the length of the other sides.


We all know the theorem, where c is the hypotenuse:

c² = a² + b²


or when we solve for c:

c = √(a² + b²)


But how do you write an application that finds the value of c?

As a developer, you immediately start breaking it down into core tasks: addition, squaring (or multiplication), square rooting.

But your students aren't going to think this way. So far, they've been introduced to problems for which there was an explicitly existing tool. Their first few challenges were all one-trick-ponies, i.e. they could be solved by knowing a particular keyword and implementing it.  

Logically, they are going to expect that this challenge (just like the ones before) will have a simple one-keyword-answer, which it doesn't. Walk them through it. Tell them there is no short answer to this, they must cobble something together with what they know. 

Present the challenge to your students, and ask how they are going to solve this. If no one starts breaking down the problem, ask them to do the math manually: 


  What is the length of the hypotenuse when the length of the other sides is 3 and 4?


When they tell you the right answer (5), ask them to show their working out. What you will get is a written-word-pseudo-code answer to the programming challenge. Something along the lines of:


  I squared the 3 and the 4, I added them together, and then I calculated the square root.


As they explain this, write it down:


Square the numbers
Add them together
Take the square root


And now ask them if they know how to solve any of these bullet points individually. 

Now, you will get students who see that the complex challenge (Pythagorean theorem) is nothing more than a combination of simpler challenges (multiplication/squaring, addition, square rooting) which they already know the answer to.

To really sell it, you could enforce that the challenge must explicitly break down the problem into these three steps:


Enforce that students make four separate methods:


calculateSquare(x)
add(x,y)
calculateSquareRoot(x) 
calculateHypotenuse(a,b) where you've explicitly disallowed any direct calculation and are only allowing them to declare/assign variables and call the other three methods.

If the students still seem to struggle doing this on their own, split the students into groups of three, where each student tackles one of the three "simple" methods. 


When they are finished creating their own methods, they can then work together at implementing the main calculateHypotenuse(a,b) method which will rely on the methods they just created.