As David points out, certain types of gradient fills can be relatively easy to implement.
Depending on the graphics library you are using, many filled shapes (such as rectangles, triangles and circles) are usually rasterized by the library into horizontal “scan lines” (in a loop incrementing the Y-axis position). For example, in the case of a filled circle primitive, one generally “walks” along the Y-axis range, computing the start and endpoints of the horizontal lines that intercept the circumference. If you want a vertical (top-to-bottom) gradient, you would simply compute a new fill color during each Y loop iteration.
Computing the gradient itself is easy: most people simply use a linear interpolation algorithm to blend between two RGB values (the color stops) depending on the progression along the Y axis (in the case of vertical gradients). i.e., if you are blending from blue (0x00,0x00,0xFF) to red (0xFF,0x00,0x00) on a circle that ranges from Y=0 to Y=100, the color used at Y=25 might be (0x3F,0x00,0xBF).
Vertical gradients tend to be easier simply because most graphics libraries rasterize into horizontal scan lines for efficiency (due to pixel arrangement in memory). Horizontal gradients are done in a similar same way conceptually (i.e. you would generally walk the X axis producing vertical strips of the same color). The key here with horizontal and vertical gradients is that one is often outputting lines at a time (of a single color), enabling relatively fast rendering. The moment you start to use arbitrary-angled gradients, it is no longer straightforward to output strips of pixels with the same pixel value — which usually means per-pixel rendering instead of line-based rendering (with impact on performance since a color computation is now performed for every pixel).
Once you have the basic gradient blend, you might want to explore non-linear gradients or piece-wise linear gradients. For example, to create gradient buttons in my GUI library, it was a simple matter to define a gradient with 3 color stops (the start, midpoint and end), with a position (%) for the midpoint. This way, one can create an effect where the shape has a gradual gradient progression that then accelerates to a different color at the bottom, for example.